#!/bin/bash
#
# tinycorrect.sh -- detect, report, suggest and correct the issues in a Markdown document
#
# Copyright (C) 2022 Wu Zhangjin <falcon@ruma.tech>, All Rights Reserved.
#

# ------------------------------
# Configurations and Arguments
# ------------------------------

# Accept user inputs
src="$1"
[ -z "$template" ] && template="$2"
[ -z "$modules" ] && modules="$3"
[ -z "$arguments" ] && arguments="$4"

# ------------------------------
# Helpers
# ------------------------------

symbols ()
{
  local symbol="$1"
  local length="$2"

  local i=0
  for i in `seq 1 $length`
  do
    echo -n "$symbol"
  done

  return 0
}

log_print ()
{
  [ "x$logging" = "x1" ] && echo -e "${logspaces}\033[34mLOG:\033[0m $1"

  return 0
}

info_print ()
{
  echo -e "\033[1;32mINFO:\033[0m $1"

  return 0
}

warn_print ()
{
  echo
  echo -e "${logspaces}\033[33mWARN:\033[0m $1"
  echo

  return 0
}

err_print ()
{
  echo
  echo -e "${logspaces}\033[31mERR:\033[0m $1"
  echo

  return 0
}

ask_print ()
{
  question="$1"
  answer=""

  echo
  echo -ne "${logspaces}\033[36mQuestion:\033[0m $question "
  read answer
  echo
}

cat_print ()
{
  local in="$1"
  local t="$2"

  if [ -n "$t" ]; then
    echo
    echo -e "${logspaces}\033[34m$t:\033[0m"
    echo "${logspaces}${logborder_cat}"
  fi

  echo
  if [ -f "$in" ]; then
    cat "$in" | sed -e "s%^%$logspaces%g"
  else
    echo "${logspaces}$in"
  fi
  echo

  if [ -n "$t" ]; then
    echo "${logspaces}${logborder_cat}"
  fi

  return 0
}

is_corrected ()
{
  local f="$1"

  local stat="$(git status -s "$f")"

  [ -z "$stat" ] && return 1

  return 0
}

correct_stat ()
{
  local f="$1"
  local errs="$2"

  local stat="$(git diff --shortstat "$f" | sed -e 's/1 file changed, //g')"

  local errs_str="errors"
  [ -z "$errs" -o "$errs" = "0" -o "$errs" = "1" ] && errs_str="error"

  if [ -z "$stat" ]; then
    stat="$(git status "$f" | tail -1), $errs $errs_str(*)"
  else
    stat="$stat, $errs $errs_str(*)"
  fi

  stat="$(echo $stat | sed -e 's/^ *//g')"

  echo "$stat"
}

stat_print ()
{
  local f="$1"
  local m="$2"
  local errs="$3"

  stat="$(correct_stat "$f" $errs)"

  echo -e "\033[32mSTAT:\033[0m Finished [$m] module"

  echo
  echo "$logspaces$stat"
  echo

  return 0
}

commit ()
{
  local f="$1"
  local m="$2"
  local p="$3"
  local ret=0

  local tool_module=${tool_raw}-${m}
  if [ -n "$p" ]; then
    git add $p $f
    git commit -s -m "$file_raw: commit correct result of ${tool_module}"
    ret=$?
    git checkout -- "$f"
  else
    echo
    local t=$(mktemp)
    git add $f
    git commit -s -m "$file_raw: commit correct result of ${tool_module}" > $t
    ret=$?
    cat_print "$t" "Commit Log"
    rm $t
  fi

  if [ $ret -ne 0 ]; then
    err_print "Failed to commit correct result of [$m] module"
    return 1
  fi
  echo

  return 0
}

sign_init ()
{
  local f="$1"
  local sign_line=""

  sign_line=$(sed -ne "/^> $def_corrector: /p" "$f" | head -1)

  # Drop the old sign
  sed -i -e "/^> $def_corrector: /d" "$f"

  echo "$sign_line"
}

sign_fini ()
{
  local f="$1"
  local sign_line="$2"

  # Add it back
  [ -n "$sign_line" ] && sed -i -e "1i${sign_line}" "$f"
}

sign_it ()
{
  local f="$1"
  local c="$2"

  # Drop the old sign
  sed -i -e "/^> $def_corrector: /d" "$f"

  # Add our new sign
  local corrector_prefix="> $def_corrector:"
  local corrector_len=$(echo -n "$corrector_prefix" | wc -c)
  local correctors="$(echo $c | sed -e 's/^ //g')"

  local max_len=$(sed -ne '0,/^#/{/^> /{/:/{p;=}}}' "$f" | sed -e 'N;s:\n: :' | gawk 'BEGIN{ m=0; }{ x=match($0,/:/); if (x > m) m=x; }END{ printf("%d\n",m); }')
  [ $max_len -lt $corrector_len ] && max_len=$corrector_len
  local spaces_len=$((max_len - corrector_len))
  local spaces="$(symbols ' ' $spaces_len)"

  sed -i -e "1i${corrector_prefix} ${spaces}[${tool_formal}]($tool_repo) ${tool_version} - [$correctors]<br/>" "$f"
}

# exam_print: print target example file or target section in the default template example file

exam_print ()
{
  local exam_name="$1"
  local exam_title=""
  local t=""

  exam_file=$EXAMPLES_DIR/${exam_name}.md
  [ ! -f "$exam_file" ] && exam_file=$template_example

  for t in $exam_titles
  do
    echo $t | grep -i -q "$exam_name"
    [ $? -eq 0 ] && exam_title="$t" && break
  done

  echo
  echo -e "$logspaces\033[35mExample:\033[0m"
  echo "${logspaces}${logborder_exam}"

  local exam_filename="$(basename "$exam_file")"

  if [ -z "$exam_title" ]; then
    cat_print "$exam_file" | sed -e "s%^%${logspaces}${exam_filename}:%g"
  else
    local s=0
    if [ "$exam_name" = "toc" ]; then
      s=1
      cat "$exam_file" | sed -e '/^\s*```/,/^\s*```/d' | grep --color=auto --label "${logspaces}${exam_filename}" -H  "^##*"
    elif [ "$exam_name" = "header" ]; then
      if [ "$translate" = "0" ]; then
        s=1
        cat "$exam_file" | sed -ne '0,/^#/p' | sed -e '$d' | grep --color=auto --label "${logspaces}${exam_filename}" -H  "^> "
      fi
    fi

    [ $s -eq 0 ] && sed -E -ne "/^## *($exam_title)/,/^##/p" "$exam_file" | sed -e '$d' | sed -e "s%^%${logspaces}${exam_filename}:%g"
  fi

  echo "${logspaces}${logborder_exam}"

  return 0
}

# Add valid check, module argument must be in available_modules list
is_valid ()
{
  local m="$1"
  local a="$2"

  if [ "$m" = "$a" ]; then
    _available_modules="$available_modules"
  else
    eval _available_modules="\$available_$a"
  fi

  echo " $_available_modules " | grep -q " $m "
  [ $? -ne 0 ] && return 1

  return 0
}

# Add abstract check, like typeset='pangu autocorrect misc', words='pycorrector xmnlp'
is_abstract ()
{
  local m="$1"
  local m_val=""

  eval m_val=\${$m}

  echo "$m_val" | grep -q '^[a-z]\{1,\}[-a-zA-Z0-9_]*'
  [ $? -ne 0 ] && return 1

  echo " $abstract_modules " | grep -q " $m "
  [ $? -ne 0 ] && return 1

  return 0
}

# rawname: get raw file name of target file

rawname ()
{
  local f="$1"

  basename "$f" | sed -E -e "s/^[0-9]*[-_]*(.*)$def_types/\1/g"
}

# Header Info
header_value ()
{
  local f="$1"
  local h="$2"

  grep -i '^> '$h':' "$f" | sed -e 's%^> [^:]*: *\(.*\).*%\1%g;s%\(<br/>\)*%%g' | tr -d '\n'
}

config_revisor ()
{
  local f="$1"
  local h="$2"
  local failprompt=0

  if [ -f "$template_authors" ]; then
    local t=$(mktemp)
    grep "^-" "$template_authors" | sed -e "s%^- *%- %g;=" | sed -e 'N;s:\n-: :' > $t
    cat_print "$t" "$h List"
    rm $t
  fi

  local author=""
  while :;
  do
    if [ -f "$template_authors" ]; then
      if [ $failprompt -eq 0 ]; then
        ask_print "Which one is your revisor (Choose the number or name)? "
      elif [ $failprompt -eq 1 ]; then
        ask_print "No one matched, Choose the number or name again? "
      else
        ask_print "No one matched, please input the revisor name yourself? "
      fi

      if [ $failprompt -lt 2 -a -n "$answer" ]; then
        local rege="^($(echo "$answer" | sed -e 's%[,; ]% |%g') )"
        author="$(grep "^-" "$template_authors" | sed -e "s%^- *%- %g;=" | sed -e 'N;s:\n-: :' | tr -s ' ' | grep -i -E "$rege" | cut -d ' ' -f2- | tr '\n' ';' | sed -e 's/;$//g')"
      else
        author="$answer"
      fi
    else
      ask_print "Please input the revisor name yourself? "
      author="$answer"
    fi

    author="$(echo $author | sed -e 's/^ *//g;s/ *$//g')"

    if [ -n "$author" ]; then
      echo "$author" | grep -q ';'
      if [ $? -eq 0 ]; then
        ask_print "Are '$author' your revisors? (y/n)"
      else
        ask_print "Is '$author' your revisor? (y/n)"
      fi
      [ "$answer" = "y" ] && break
    fi
    ((failprompt++))
  done

  # Update the value of 'Revisor'
  eval `eval echo \\$h=\''$author'\'`
}

config_value ()
{
  local f="$1"
  local n="$2"

  # Only for revisor configure currently
  [ "$n" != "Revisor" ] && return 0

  config_revisor "$f" "$n"
}

# newline: clean up newline, uniq newline and only reserve a '\n' at the end

newline ()
{
  local f="$1"
  local t=$(mktemp)

  # Backup for operation
  cp "$f" $t

  # Uniq empty newlines (two "\n") and at least add one newline (two "\n") at the end
  cat $t | gawk '
    # Reserve two "\n" at the begin
    BEGIN { x = 1; } {
      if (length != 0) {
        printf("%s\n", $0);
        x = 1;
      } else if (x == 1) {
        x = 0;
        printf("%s\n", $0);
      }
    # Always reserve two "\n" at the end
    } END { if (x == 1) printf("\n"); }' > "$f"

  # Remove the last "\n", and reserve at least one "\n"
  sed -i -e '$d' "$f"

  # Remove the temp file
  rm $t

  return 0
}

# Get language: # 0: chinese, 1: english, 2: both

lang ()
{
  local in="$1"

  if [ -f "$in" ]; then
    grep -q -P "$chinese" "$in"
  else
    echo "$in" | grep -q -P "$chinese"
  fi

  # Pure English (no Chinese found)
  if [ $? -ne 0 ]; then
    echo 1
  else
    if [ -f "$in" ]; then
      grep -q '[a-zA-Z]' "$in"
    else
      echo "$in" | grep -q '[a-zA-Z]'
    fi
    # Pure Chinese (no English found)
    if [ $? -ne 0 ]; then
      echo 0
    else
      echo 2
    fi
  fi
}

# Absolute path
abspath ()
{
  local f="$1"

  local dir="$(cd $(dirname "$f")/ && pwd)"
  local name="$(basename "$f")"

  echo "$dir/$name"
}

# Absolute directory
absdir ()
{
  local f="$1"

  local dir="$(cd $(dirname "$f")/ && pwd)"

  echo "$dir"
}

# Safe delimiter

delimiter ()
{
  local in="$1"

  # Try different delimiters
  local dc=", | ; ~ _ = @ # %"

  for d in $dc
  do
    if [ -f "$in" ]; then
      grep -q "$d" "$in"
    else
      echo "$in" | grep -q "$d"
    fi
    [ $? -ne 0 ] && break
  done

  echo "$d"
}

# Clean tmp files
clean ()
{
  local t="$1"

  [ -f "$t" ] && rm $t

  return 0
}

# gitcheck: check git repo

gitcheck ()
{
  local f="$1"
  local ret=0

  # Allow skip it
  [ "$gitcheck" = "0" ] && return 0
  echo "$skips $skip" | grep -q gitcheck && return 0

  # Get file status
  # FIXME: the return value is not the same as running manually, it always return 0 even thre is a failure
  status="$(git status -s "$f" 2>&1)"

  # If in current repo and every change is committed, it is ok to continue
  [ -z "$status" ] && return 0

  # Shorten the status, use status string instead of content, for the content may change while the system use another language
  local status_len="$(echo $status | sed -e 's/^ //g' | sed -e "s% $f%%g" | wc -c)"

  # Report the erroris and tell users how to continue
  # outside: fatal: /path/to/xxx: '/path/to/xxx' is outside repository at '/path/to/...'
  # inside: ?? /path/to/file, M /path/to/file; A /path/to/file
  if [ $status_len -ge 30 ]; then
    err_print "Please work in target git repo of $f, create one if required"
    cat_print "$status" "Errors found by gitcheck"
    return 1
  fi

  # Target file should be added in repo without any uncommitted changes
  err_print "To protect your data, please add and commit changes of '$f' at first"

  local t=$(mktemp)
  git status "$f" > $t
  cat_print "$t" "Please check current git status"
  rm $t

  return 1
}

# ------------------------------
# Modules
# ------------------------------

# typeset: for basic Chinese type setting correction
# Reference: [中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines)

pangu_fix_begin ()
{
  return 0
}

pangu_fix_end ()
{
  local f="$1"

  # Should support such style: [中文 English](https://example.com/xxx/中文 English.html "链接 提示 信息")
  # ref: https://markdown.com.cn/basic-syntax/links.html
  if [ "$pangu_rule_spacing" = "1" ]; then
    # This issue is introduced by the following code
    #
    # " 汉字与其前后的英文字符、英文标点、数字间增加空白。
    # if g:pangu_rule_spacing == 1
    #  silent! execute firstline . ',' . lastline . 's/\([\u4e00-\u9fa5\u3040-\u30FF]\)\([a-zA-Z0-9]\)/\1 \2/g'
    #  silent! execute firstline . ',' . lastline . 's/\([a-zA-Z0-9]\)\([\u4e00-\u9fa5\u3040-\u30FF]\)/\1 \2/g'
    # endif

    # Images and urls name should not have whitespaces, but pangu introduced whitespaces between Chinese and English, which is wrong.
    local t=$(mktemp)
    sed -ne '/\[.*\](/{s/\([!]*\[[^\[]*\](\)/\n\1/g;s/\(]([^(]*)\)/\1\n/g;s/.*](\(.*\)).*/\1/gp}' "$f" | grep ' ' > $t
    grep -E "^\[[a-zA-Z0-9#]{1,}\]: " "$f" | cut -d':' -f2- | sed -e 's/^ //g' | grep ' ' >> $t

    local d=""

    while read url
    do
      echo "$url" | grep -q "^[[:space:]]$"
      [ $? -eq 0 ] && continue

      d=$(delimiter "$url")

      # Remove whitespaces in urls
      _url="$(echo $url | sed -e "s${d}&${d}\\\&${d}g" | tr "'" "\"" | tr '(' '"' | tr ')' '"' | sed -E -e '/["]/!{s/ //g};/"/{h;s/([^"]*) .*/\1 /g;s/ //g;x;s/[^"]* (".*")/\1/g;H;x;s:\n: :}')"
      sed -i -e "/\[.*\](/{/](.*);\s*$/!{s$d\[\s*\(.*[^ \t]\)\s*\]($url)$d[\1]($_url)${d}g;s%\s*$%%g}}" "$f"
      sed -i -e "/^\[[a-zA-Z0-9#]\{1,\}\]: /{s$d\(^\[[a-zA-Z0-9#]\+\]: \)$url$d\1$_url${d}g;s%\s*$%%g}" "$f"
    done < $t

    # Remove the temp file
    rm $t
  fi

  # Remove whitespaces around chinese symbols
  spaces "$f"

  # Fix up the mermaid [], 【】-> []
  sed -i -e '/```mermaid/,/```/{s%【\([^【】]*\)】\(\s*-->\)%[\1]\2%g;s%\(\s*-->\s*[A-Za-z]\)【\([^【】]*\)】%\1[\2]%g}' "$f"

  # Fix up 《》-> <>
  sed -i -e '/```/,/```/{/\(^\s*[$#]\|adb\|grep\|sed\|awk\|apt\|dpkg\)/{s% 《\([^《》]*\)》% <\1>%g}}' "$f"

  return 0
}

pangu ()
{
  local f="$1"

  # Basic type setting correction

  # TinyLab clone of pangu.vim have fixed some bugs, they will be upstreamed in the future
  # 1. ![]() -> ！[]()
  # 2. [![]()] -> [！[]()]()
  # 3. \\ -> 、\
  # 4. [][]（）-> [][]()

  PANGU_REPO=https://gitee.com/tinylab/pangu-vim
  PANGU_BRANCH=tico
  PANGU_DIR="$TOP_DIR/3rd-party/pangu-vim"
  PANGU_FILE="$PANGU_DIR/pangu.vim"
  PANGU_VERN="$PANGU_DIR/version"
  PANGU_MINV="20231024"

  # Download if missing or not match
  local pangu_outofdate=0
  if [ -f "$PANGU_FILE" ]; then
    local download_version=0
    if [ -f "$PANGU_VERN" ]; then
      version=$(cat "$PANGU_VERN")
      if [ $version -lt $PANGU_MINV ]; then
        download_version=1
      fi
    fi

    if [ ! -f "$PANGU_VERN" -o $download_version -eq 1 ]; then
      log_print "Download Pangu Version file"
      rm -rf "$PANGU_VERN"
      wget -c "$PANGU_REPO/raw/$PANGU_BRANCH/plugin/version" -O "$PANGU_VERN"
      [ $? -ne 0 ] && rm -rf "$PANGU_VERN" && err_print "Failed to download pangu version" && return 1
    fi

    version=$(cat "$PANGU_VERN")
    if [ $version -lt $PANGU_MINV ]; then
      pangu_outofdate=1
    fi
  fi

  if [ ! -f "$PANGU_FILE" -o $pangu_outofdate -eq 1 ]; then
    [ ! -d "$PANGU_DIR" ] && mkdir -p $PANGU_DIR
    log_print "Download Pangu Plugin for typesetting correction"
    rm -rf "$PANGU_FILE"
    wget -c "$PANGU_REPO/raw/$PANGU_BRANCH/plugin/pangu.vim" -O "$PANGU_FILE"
    [ $? -ne 0 ] && rm -rf "$PANGU_FILE" && err_print "Failed to download pangu plugin" && return 1
  fi

  # Default settings, these variables should be seen in pangu_fix_end()
  if [ "$modules" = "pangu" -o "$typeset" = "pangu" ]; then
    [ -z "$pangu_rule_spacing" ] && pangu_rule_spacing="1"
  else
    [ -z "$pangu_rule_spacing" ] && pangu_rule_spacing="1"
  fi

  # Fix up type setting issues by an external tool, here use vim plugin: pangu
  local t=$(mktemp)
  vim -T ansi --noplugin -n -e -c "source $PANGU_FILE" -c "let g:pangu_rule_spacing=$pangu_rule_spacing" -c 'PanguAll' -c 'wq' "$f" | col -bp > $t 2>&1
  cat_print "$t" "Pangu Log"
  rm $t

  # Correct the buggy operations
  pangu_fix_end "$f"

  return 0
}

hide_words ()
{
  local words="$spellcheck_words_disabled"
  local w=""

  for w in $words
  do
    w=$(echo "$w" | tr '=' ' ')
    _w=$(echo "$w" | tr '=' ' ' | sed -e 's/\([a-zA-Z]\)/--\1--/g')
    sed -i -e "s%$w%$_w%g" "$f"
  done

  return 0
}

unhide_words ()
{
  local words="$spellcheck_words_disabled"

  local w=""

  for w in $words
  do
    _w=$(echo "$w" | tr '=' ' ' | sed -e 's/\([a-zA-Z]\)/--\1--/g')
    sed -i -e "/$_w/{s%--\([a-zA-Z]\)--%\1%g}" "$f"
  done

  return 0
}

autocorrect_fix_begin ()
{
  local f="$1"

  # Workaround OSs, it can not be replaced to OSS
  hide_words

  return 0
}

autocorrect_fix_end ()
{
  local f="$1"

  # Restore OSs
  unhide_words

  # For Cloud Lab based Labs
  sed -i -e "s%^Linux lab%Linux Lab%g;s% Linux lab % Linux Lab %g;s% Linux lab$%Linux Lab%g" "$f"
  sed -i -e "s%^Linux 0.11 lab%Linux 0.11 Lab%g;s% Linux 0.11 lab % Linux 0.11 Lab %g;s% Linux 0.11 lab$%Linux 0.11 Lab%g" "$f"
  sed -i -e "s%^Markdown lab%Markdown Lab%g;s% Markdown lab % Markdown Lab %g;s% Markdown lab$%Markdown Lab%g" "$f"

  # Add missing whitespaces around “”
  vim -T ansi --noplugin -n -e -c "silent! %s/“\s*\([^“”]*[-+=@_~a-zA-Z0-9.()\u4e00-\u9fa5\u3040-\u30FF]\)\s*”\s*\([\u4e00-\u9fa5\u3040-\u30FF]\)/“\1” \2/g" -c 'silent! wq' "$f" > /dev/null
  vim -T ansi --noplugin -n -e -c "silent! %s/\([^ \t，。：？；、（）〖〗【】『』「」]\)“\s*\([^“”]*[-+=@_~a-zA-Z0-9.()\u4e00-\u9fa5\u3040-\u30FF]\)\s*”\s*\([\u4e00-\u9fa5\u3040-\u30FF]\)/\1 “\2” \3/g" -c 'silent! wq' "$f" > /dev/null
  vim -T ansi --noplugin -n -e -c "silent! %s/\([\u4e00-\u9fa5\u3040-\u30FF]\)\s*“\s*\([^“”]*[-+=@_~a-zA-Z0-9.()\u4e00-\u9fa5\u3040-\u30FF]\)\s*”/\1 “\2”/g" -c 'silent! wq' "$f" > /dev/null
  vim -T ansi --noplugin -n -e -c "silent! %s/\([\u4e00-\u9fa5\u3040-\u30FF]\)\s*“\s*\([^“”]*[-+=@_~a-zA-Z0-9.()\u4e00-\u9fa5\u3040-\u30FF]\)\s*”\([^ \t，。：？；、（）〖〗【】『』「」]\)/\1 “\2” \3/g" -c 'silent! wq' "$f" > /dev/null

  vim -T ansi --noplugin -n -e -c "silent! %s/“\s*\([^“”]*[\u4e00-\u9fa5\u3040-\u30FF]\)\s*”\s*\([-+=@_~a-zA-Z0-9.()]\)/“\1” \2/g" -c 'silent! wq' "$f" > /dev/null
  vim -T ansi --noplugin -n -e -c "silent! %s/\([^ \t，。：？；、（）〖〗【】『』「」]\)“\s*\([^“”]*[\u4e00-\u9fa5\u3040-\u30FF]\)\s*”\s*\([-+=@_~a-zA-Z0-9.()]\)/\1 “\2” \3/g" -c 'silent! wq' "$f" > /dev/null
  vim -T ansi --noplugin -n -e -c "silent! %s/\([-+=@_~a-zA-Z0-9.()]\)\s*“\s*\([^“”]*[\u4e00-\u9fa5\u3040-\u30FF]\)\s*”/\1 “\2”/g" -c 'silent! wq' "$f" > /dev/null
  vim -T ansi --noplugin -n -e -c "silent! %s/\([-+=@_~a-zA-Z0-9.()]\)\s*“\s*\([^“”]*[\u4e00-\u9fa5\u3040-\u30FF]\)\s*”\([^ \t，。：？；、（）〖〗【】『』「」]\)/\1 “\2” \3/g" -c 'silent! wq' "$f" > /dev/null

  vim -T ansi --noplugin -n -e -c "silent! %s/‘\s*\([^‘’]*[-+=@_~a-zA-Z0-9.()]\)\s*’\s*\([\u4e00-\u9fa5\u3040-\u30FF]\)/‘\1’ \2/g" -c 'silent! wq' "$f" > /dev/null
  vim -T ansi --noplugin -n -e -c "silent! %s/\([^ \t，。：？；、（）〖〗【】『』「」]\)‘\s*\([^‘’]*[-+=@_~a-zA-Z0-9.()]\)\s*’\s*\([\u4e00-\u9fa5\u3040-\u30FF]\)/\1 ‘\2’ \3/g" -c 'silent! wq' "$f" > /dev/null
  vim -T ansi --noplugin -n -e -c "silent! %s/\([\u4e00-\u9fa5\u3040-\u30FF]\)\s*‘\s*\([^‘’]*[-+=@_~a-zA-Z0-9.()]\)\s*’/\1 ‘\2’/g" -c 'silent! wq' "$f" > /dev/null
  vim -T ansi --noplugin -n -e -c "silent! %s/\([\u4e00-\u9fa5\u3040-\u30FF]\)\s*‘\s*\([^‘’]*[-+=@_~a-zA-Z0-9.()]\)\s*’\([^ \t，。：？；、（）〖〗【】『』「」]\)/\1 ‘\2’ \3/g" -c 'silent! wq' "$f" > /dev/null

  vim -T ansi --noplugin -n -e -c "silent! %s/‘\s*\([^‘’]*[\u4e00-\u9fa5\u3040-\u30FF]\)\s*’\s*\([-+=@_~a-zA-Z0-9.()]\)/‘\1’ \2/g" -c 'silent! wq' "$f" > /dev/null
  vim -T ansi --noplugin -n -e -c "silent! %s/\([^ \t，。：？；、（）〖〗【】『』「」]\)‘\s*\([^‘’]*[\u4e00-\u9fa5\u3040-\u30FF]\)\s*’\s*\([-+=@_~a-zA-Z0-9.()]\)/\1 ‘\2’ \3/g" -c 'silent! wq' "$f" > /dev/null
  vim -T ansi --noplugin -n -e -c "silent! %s/\([-+=@_~a-zA-Z0-9.()]\)\s*‘\s*\([^‘’]*[\u4e00-\u9fa5\u3040-\u30FF]\)\s*’/\1 ‘\2’/g" -c 'silent! wq' "$f" > /dev/null
  vim -T ansi --noplugin -n -e -c "silent! %s/\([-+=@_~a-zA-Z0-9.()]\)\s*‘\s*\([^‘’]*[\u4e00-\u9fa5\u3040-\u30FF]\)\s*’\([^ \t，。：？；、（）〖〗【】『』「」]\)/\1 ‘\2’ \3/g" -c 'silent! wq' "$f" > /dev/null

  # Remove whitespace between chinese and '-english'
  vim -T ansi --noplugin -n -e -c "silent! %s/\[\(.*\)\([\u4e00-\u9fa5\u3040-\u30FF]\)\s*-\([0-9]\)\(.*\)\]/[\1\2-\3\4]/g" -c 'silent! wq' "$f" > /dev/null

  return 0
}

codeblock_fix_begin ()
{
  local f="$1"
  local t=$(mktemp)

  local notstd_cb=0

  # Find another style of codeblock, no '```' use in such case
  local notstd_cb_total=$(cat "$f" | sed -e 's%^ *$%%g;s%\t%    %g' | sed -e '/^\s*```/,/^\s*```/{s/.*//g}' \
    | gawk 'BEGIN{ spaces=0; space0=0; indent0=0; indenttotal=0; delta0=0; linebegin=0; deltabegin=0; }{
      nlist=match($0, /^ *[0-9]+\. /);
      glist=match($0, /^ *[+*-] /);
      oline=match($0, /^ *[^ 0-9+*-]/);
      spaces=match($0, /[^ ]/);

      # number ordered list: at least 3 whitespaces
      # non-orderded list: at least 2 whitespaces
      indent=0
      if (nlist != 0) {
        indent=3;
      }
      if (glist != 0) {
        indent=2;
      }
      if (oline != 0) {
        indent=0;
      }

      # Get real spaces
      ((spaces--))

      # Ignore the new indent data if already in codeblock
      if (spaces > 0 && (deltabegin >= 4))
        indent=0

      # Reset indent total while meet normal content
      if (spaces == 0) {
        indent0=0;
        indenttotal=0;
      }

      # Increase the indent total
      indenttotal = indenttotal + indent;

      # Ignore the empty newlines
      if (spaces != -1) {
        delta = spaces - indenttotal;
        if (linebegin == 0 && delta >= 4 && (spaces - spaces0 > 0)) {
          linebegin=NR;
          deltabegin=delta;
          printf("codeblock found\n");
          exit 0
        }

        if (linebegin != 0 && delta < 4 && (delta0 >= 4 || delta0 == 0)) {
          linebegin=0
          deltabegin=0
        }

        indent0=indent
        spaces0=spaces;
        delta0=delta;
      }
    }' | wc -l)

  [ $notstd_cb_total -gt 0 ] && notstd_cb=1

  # Do nothing if not mixed
  [ $notstd_cb -eq 0 ] && return 0

  # Convert multiple newlines to one
  newline "$f"

  # Get codeblock list: linebegin, lineend, indent
  cat "$f" \
    | sed -e 's%^ *$%%g;s%\t%    %g' | sed -e '/^\s*```/,/^\s*```/{s/.*//g}' \
    | gawk 'BEGIN{ spaces=0; space0=0; indent0=0; indenttotal=0; delta0=0; linebegin=0; deltabegin=0; }{
      nlist=match($0, /^ *[0-9]+\. /);
      glist=match($0, /^ *[+*-] /);
      oline=match($0, /^ *[^ 0-9+*-]/);
      spaces=match($0, /[^ ]/);

      indent=0
      if (nlist != 0) {
        indent=3;
      }
      if (glist != 0) {
        indent=2;
      }
      if (oline != 0) {
        indent=0;
      }

      ((spaces--))

      if (spaces > 0 && (deltabegin >= 4))
        indent=0

      if (spaces == 0) {
        indent0=0;
        indenttotal=0;
      }

      indenttotal = indenttotal + indent;

      if (spaces != -1) {
        delta = spaces - indenttotal;
        # Detect codeblock begin
        if (linebegin == 0 && delta >= 4 && (spaces - spaces0 > 0)) {
          linebegin=NR;
          deltabegin=delta;
        }

        # Detect codeblock end
        if (linebegin != 0 && delta < 4 && (delta0 >= 4 || delta0 == 0)) {
          printf("%s %s %s\n", linebegin, NR, deltabegin);
          lineend=NR
          linebegin=0
          deltabegin=0
        }

        indent0=indent
        spaces0=spaces;
        delta0=delta;
      }
    }END {
        # If codeblock end is the last line, use NR+2 as the end line
        spaces=-1
        if (linebegin != 0 && delta <= 4 && (delta0 >= 4 || delta0 == 0))
          printf("%s %s %s\n", linebegin, NR+2, deltabegin);
    }' > "$t"

  # Wrap the codeblocks with '```'
  local n=0
  local spaces_orig=''
  local spaces_new=''
  local b=0
  local e=0
  local indent=0
  local total=$(cat "$f" | wc -l)
  while read begin end indent
  do
    spaces_orig="$(symbols ' ' $indent)"
    ((indent-=4))
    spaces_new="$(symbols ' ' $indent)"

    # Increase positions by the number of added newlines
    ((b = begin + n))
    ((e = end + n))

    # Append newlines if end > total
    if [ $e -gt $total ]; then
      echo >> "$f"
      echo >> "$f"
    fi

    # Replace target line to '```'
    sed -i -e ''${b}'i```tico_codeblock_fix' "$f"
    sed -i -e ''${e}'i```tico_codeblock_fix' "$f"

    # Strip the append newlines
    if [ $e -gt $total ]; then
      sed -i -e '$d' "$f"
      sed -i -e '$d' "$f"
    fi

    # Two newlines requires to increase the original line number by 2
    ((n+=2))
    ((total+=2))
  done < "$t"

  # Remove temp file
  rm $t

  return 0
}

codeblock_fix_end ()
{
  local f="$1"

  sed -i -e '/^\s*```tico_codeblock_fix/d' "$f"

  newline "$f"

  return 0
}

autocorrect ()
{
  local f="$1"
  local t=$(mktemp)
  local ret=0
  local bak=$(mktemp)

  AUTOCORRECT_REPO=https://gitee.com/tinylab/rust-autocorrect
  AUTOCORRECT_BRANCH=tico
  AUTOCORRECT_DIR="$TOP_DIR/3rd-party/rust-autocorrect"

  # Use compressed release
  local variant=upx
  AUTOCORRECT_FILE_BASE=autocorrect.$variant
  AUTOCORRECT_MD5_BASE=md5sum.$variant
  AUTOCORRECT_VERN_BASE=version

  AUTOCORRECT_FILE="$AUTOCORRECT_DIR/$AUTOCORRECT_FILE_BASE"
  AUTOCORRECT_MD5="$AUTOCORRECT_DIR/$AUTOCORRECT_MD5_BASE"
  AUTOCORRECT_VERN="$AUTOCORRECT_DIR/$AUTOCORRECT_VERN_BASE"
  AUTOCORRECT_MINV="20220916"

  # Gitee doesn't support to download big file directly, use tinylab.org instead
  local remote="tinylab"
  if [ "$remote" = "gitee" ]; then
    AUTOCORRECT_REMOTE_DIR="$AUTOCORRECT_REPO/raw/$AUTOCORRECT_BRANCH/bin"
  else
    AUTOCORRECT_REMOTE_DIR="https://tinylab.org/wp-content/uploads/tinycorrect/autocorrect"
  fi

  # Get real name
  for v in base64 upx
  do
    echo $AUTOCORRECT_FILE | grep -q "\.${variant}$"
    if [ $? -eq 0 ]; then
      AUTOCORRECT_ORIG="$AUTOCORRECT_FILE"
      AUTOCORRECT_FILE="$(echo "$AUTOCORRECT_ORIG" | sed -e "s/\.${variant}$//g")"
    fi
  done

  # Download if missing or not match
  local autocorrect_outofdate=0
  local autocorrect_download=0
  if [ -f "$AUTOCORRECT_FILE" -o -f "$AUTOCORRECT_ORIG" ]; then
    local download_version=0
    if [ -f "$AUTOCORRECT_VERN" ]; then
      version=$(cat "$AUTOCORRECT_VERN")
      if [ $version -lt $AUTOCORRECT_MINV ]; then
        download_version=1
      fi
    fi

    if [ ! -f "$AUTOCORRECT_VERN" -o $download_version -eq 1 ]; then
      log_print "Download Autocorrect Version file"
      rm -rf "$AUTOCORRECT_VERN"
      wget -c "$AUTOCORRECT_REMOTE_DIR/$AUTOCORRECT_VERN_BASE" -O "$AUTOCORRECT_VERN"
      [ $? -ne 0 ] && rm -rf "$AUTOCORRECT_VERN" && err_print "Failed to download autocorrect version" && return 1
    fi
    version=$(cat "$AUTOCORRECT_VERN")
    if [ $version -lt $AUTOCORRECT_MINV ]; then
      autocorrect_outofdate=1
    fi

    grep -q "^<html lang=" "$AUTOCORRECT_FILE"
    if [ $? -eq 0 ]; then
      err_print "Invalid file type of $(file $AUTOCORRECT_FILE), required to download a new one"
      rm -rf $AUTOCORRECT_FILE
      autocorrect_outofdate=1
    fi
  else
    autocorrect_download=1
  fi

  if [ $autocorrect_download -eq 1 -o $autocorrect_outofdate -eq 1 ]; then
    [ ! -d "$AUTOCORRECT_DIR" ] && mkdir -p $AUTOCORRECT_DIR
    log_print "Download Autocorrect Plugin for typesetting correction"

    rm -rf "$AUTOCORRECT_FILE"
    wget -c "$AUTOCORRECT_REMOTE_DIR/$AUTOCORRECT_FILE_BASE" -O "$AUTOCORRECT_FILE"
    [ $? -ne 0 ] && rm -rf "$AUTOCORRECT_FILE" && err_print "Failed to download autocorrect plugin" && return 1

    grep -q "^<html lang=" "$AUTOCORRECT_FILE"
    [ $? -eq 0 ] && err_print "Invalid file type of $(file $AUTOCORRECT_FILE)" && rm -rf "$AUTOCORRECT_FILE" && return 1

    rm -rf "$AUTOCORRECT_MD5"
    wget -c "$AUTOCORRECT_REMOTE_DIR/$AUTOCORRECT_MD5_BASE" -O "$AUTOCORRECT_MD5"
    if [ $? -eq 0 ]; then
      local md5sum_saved="$(cat $AUTOCORRECT_MD5 | cut -d ' ' -f1)"
      local md5sum_calculated="$(md5sum $AUTOCORRECT_FILE | cut -d ' ' -f1)"
      [ "$md5sum_saved" != "$md5sum_calculated" ] && rm -rf "$AUTOCORRECT_FILE" "$AUTOCORRECT_MD5" && err_print "md5sum of $AUTOCORRECT_FILE mismatch" && return 1
    else
      rm -rf "$AUTOCORRECT_FILE" "$AUTOCORRECT_MD5"
      err_print "Failed to download autocorrect md5sum file" && return 1
    fi

  fi

  # Convert base64 or upx to bin
  for v in base64 upx
  do
    echo $AUTOCORRECT_FILE | grep -q "\.${variant}$"
    if [ $? -eq 0 ]; then
      AUTOCORRECT_ORIG="$AUTOCORRECT_FILE"
      AUTOCORRECT_FILE="$(echo "$AUTOCORRECT_ORIG" | sed -e "s/\.${variant}$//g")"
      echo $variant | grep -q "\.base64"
      if [ $? -eq 0 ]; then
        base64 -d $AUTOCORRECT_ORIG > $AUTOCORRECT_FILE
      else
        mv $AUTOCORRECT_ORIG $AUTOCORRECT_FILE
      fi
    fi
  done
  chmod a+x "$AUTOCORRECT_FILE"

  # It is able to correct some words
  AUTOCORRECT_CONFIG=$AUTOCORRECT_DIR/.autocorrectrc
  [ "$autocorrect_debug" = "1" ] && AUTOCORRECT_DEBUG=--debug

  # Fix up the urls alias map autocorrect issue, let autocorrect just ignore it
  local url_map=$(mktemp)
  sed -ne '/^\s*```/,/^\s*```/!{/^\[[a-zA-Z0-9#]\{1,\}\]: /p}' "$f" | sort -t '[' -g -k 2 | uniq > $url_map

  # If url alias map found, split out them, otherwise, do nothing
  if [ -s "$url_map" ]; then
    sed -i -e '/^\s*```/,/^\s*```/!{/^\[[a-zA-Z0-9#]\{1,\}\]: /d}' "$f"
    newline "$f"
    cp "$f" "$bak"
  fi

  # Skip some special words
  def_spellcheck_words_disabled="OSs"
  spellcheck_words_disabled="$def_spellcheck_words_disabled"

  # Skip the words like '-word' or 'word-', which should not be touched
  for w in $(grep '^\s*- ' $AUTOCORRECT_CONFIG | cut -d '-' -f2- | cut -d ' ' -f2- | cut -d '=' -f1 | tr ' ' '=' | tr '[A-Z]' '[a-z]')
  do
    spellcheck_words_disabled="$spellcheck_words_disabled -${w} ${w}-"
  done

  # prepare autocorrect fix
  autocorrect_fix_begin "$f"

  # Insert '```' for nostd codeblocks
  codeblock_fix_begin "$f"

  timeout -k 2 "$timeout" $AUTOCORRECT_FILE $AUTOCORRECT_DEBUG -c $AUTOCORRECT_CONFIG --fix "$f" > $t 2>&1
  ret=$?

  # Restore notstd codeblocks
  codeblock_fix_end "$f"

  if [ -s "$url_map" ]; then
    if [ $ret -eq 0 ]; then
      echo >> "$f"
      cat $url_map >> "$f"
    else
      echo >> "$bak"
      cat $url_map >> "$bak"
      cp "$bak" "$f"
    fi
  fi
  rm "$bak"
  rm "$url_map"

  if [ -f "$t" ]; then
    local t0=$(mktemp)
    cat $t | grep -v "^[[:space:]]*$" > $t0
    cat_print "$t0" "Autocorrect Log"
    rm $t0
    rm $t
  fi

  if [ $ret -eq 0 ]; then
    autocorrect_fix_end "$f"
  else
    warn_print "Can not run autocorrect in $timeout seconds, give up"
  fi

  return 0
}

# epw: error-prone words corrector

epw_core ()
{
  local t="$1"

  # For default epws map
  local from
  local to
  local from_rege
  local to_rege
  local exception
  local fr
  local tr
  local ex

  while read from to from_rege to_rege exception
  do
    d=$(delimiter "$from_rege $to_rege $exception")

    eval ex=$exception
    eval fr=$from_rege
    eval tr=$to_rege

    if [ "$exception" ]; then
      sed -E -i -e "/$ex/!{s${d}${fr}${d}${tr}${d}g}" "$f"
    else
      sed -E -i -e "s${d}${fr}${d}${tr}${d}g" "$f"
    fi
  done < $t
  rm "$t"
}

epw_chinese ()
{
  local f="$1"

  local t=$(mktemp)

  # Grab the map out
  sed -ne '/^- /{s/^- //gp}' $template_epws_cn > $t

  # Work on map file
  epw_core "$t"

  return 0
}

epw_english ()
{
  local f="$1"
  local t=$(mktemp)

  # Grab the map out
  sed -ne '/^- /{s/^- //gp}' $template_epws_en > $t

  # Work on map file
  epw_core "$t"

  return 0
}

epw_misc ()
{
  local f="$1"

  # <kbd>Something<kbd> -> <kbd>Something</kbd>
  sed -i -e "s%<kbd>\([^<]*\)<kbd>%<kbd>\1</kbd>%g"  "$f"

  # 中文, -> 中文，
  vim -T ansi --noplugin -n -e -c "silent! %s/\([\u4e00-\u9fa5\u3040-\u30FF]\) *, */\1，/g" -c 'silent! wq' "$f" > /dev/null

  # 中文 ( ) 中文 -> 中文（ ）中文 ; ( 中文 ) -> （中文）
  vim -T ansi --noplugin -n -e -c "silent! %s/ (\s*\([\u4e00-\u9fa5\u3040-\u30FF][^()]*[^ \t]\)\s*) \{0,1\}/（\1）/g" -c 'silent! wq' "$f" > /dev/null
  vim -T ansi --noplugin -n -e -c "silent! %s/ (\s*\([^()]*[\u4e00-\u9fa5\u3040-\u30FF]\)\s*) \{0,1\}/（\1）/g" -c 'silent! wq' "$f" > /dev/null
  vim -T ansi --noplugin -n -e -c "silent! %s/\([\u4e00-\u9fa5\u3040-\u30FF]\) \{0,1\}(\s*\([^()]*[^ \t]\)\s*) \{0,1\}\([\u4e00-\u9fa5\u3040-\u30FF]\)/\1（\2）\3/g" -c 'silent! wq' "$f" > /dev/null
  vim -T ansi --noplugin -n -e -c "silent! %s/\([^]]\)(\s*\([\u4e00-\u9fa5\u3040-\u30FF][^()]*[^ \t]\)\s*) \{0,1\}\([\u4e00-\u9fa5\u3040-\u30FF]\)/\1（\2）\3/g" -c 'silent! wq' "$f" > /dev/null
  vim -T ansi --noplugin -n -e -c "silent! %s/\([^]]\)(\s*\([^()]*[\u4e00-\u9fa5\u3040-\u30FF]\)\s*) \{0,1\}\([\u4e00-\u9fa5\u3040-\u30FF]\)/\1（\2）\3/g" -c 'silent! wq' "$f" > /dev/null

  # Symbols: ') :', '] :', '} :' -> '):' ']:' '}:'
  sed -i -e '/^\s*```/,/^\s*```/!{/^    [$#]/!{/^ *`/!{/[`]*.*|\{1,\}.*[`]*/!{s/\([)\|\]\|}\) *:/\1:/g}}}}' "$f"

  return 0
}

epw ()
{
  local f="$1"

  local e=""
  for e in chinese english misc
  do
    epw_$e "$f"
  done

  return 0
}

# comments: clean up comments usage

comments ()
{
  local f="$1"
  local t=$(mktemp)
  local errs=0

  # Correct path issue
  local winpath_total=$(sed -ne '/\/\//{/:\/\//!p}' "$f"  | tr -d -c '\\\n' | grep -E -v '^$|sed|awk|grep|^-|^+|^\s*[$#]|^\s*\||\s*\|\s*$' | wc -l)
  if [ $winpath_total -ge 1 ]; then
    err_print "Windows path found in comments with '//', please correct them"
    sed -ne '/\/\//{/:\/\//!{/\\/{=;p}}}' "$f" | sed -e 'N;s%\n%:%' > "$t"
    cat_print "$t" "Potential windows paths found:" | sed -e 's%\\%\x1b[1;33m\\\x1b[0m%g'

    ask_print "Do you want to correct them automatically? (y/n) "
    if [ "$answer" = "y" ]; then
      sed -i -e '/\/\//{/:\/\//!{/\\/{s%\\%/%g}}}' "$f"
    else
      ((errs++))
    fi
  fi

  # For // style comments
  sed -i -e '/\(^\s*[$#]\|sed\|awk\|grep\|^-\|^+\|^\s*|\|\s*|\s*$\| ___\| ---\| |--\| -->\)/!{s%^//\s*\(.*\)\s*%// \1%g;s%////%//%g;s%\([^'\'':`"“‘*_ \t/]\)//\s*\(*\)\s*%\1 // \2%g;s%\(\s*[^'\'':`"“‘*_ \t/]\)//\s*\(.*\)\s*%\1// \2%g;/^\s*```/,/^\s*```/!{s%^ //\s*%// %g};s%\s*$%%g}' "$f"

  # For /* */ style comments
  sed -i -e '/\(^\s*[$#]\|sed\|awk\|grep\|^-\|^+\|^\s*|\|\s*|\s*$\| ___\| ---\| |--\| -->\)/!{s%\(/\*\{1,\}\)\s*\(.*\)\s*\*/%\1 \2 */%g;s%\(\s*\)\(/\*\{1,\}\)\s*\(.*\)\s*\*/%\1\2 \3 */%g;s%\([^ '\''`"“‘*_\t]\)\(/\*\{1,\}\)\s*\(.*\)\s*\*/%\1 \2 \3 */%g;s%^\(\s*\)\(/\*\{1,\}\)\s*%\1\2 %g;/^[-+]\s*\*\//!{s%\([^ \t]\)\s*\*/%\1 */%g;s%^\*/% */%g};/^\s*```/,/^\s*```/!{s%^ \(/\*\{1,\}\)\s*%\1 %g};s%\s*$%%g;s%\([。：？；）〗】』」]\) \*/%\1*/%g}' "$f"

  # Backup for operation
  cp "$f" $t

  # Uniq commentd empty newlines (^//$)
  cat $t | gawk '
    BEGIN { x = 0; } {
      if ($0 != "//") {
        printf("%s\n", $0);
        x = 1;
      } else if (x == 1) {
        x = 0;
        printf("%s\n", $0);
      }
    }' > "$f"

  # Delete the begining and ending '//' if there is
  cat "$f" | \
    gawk 'BEGIN{ commentbegin=0; commentflag0=0; emptycommentflag=0; spaces0=0; spacesmin=0; }{
      commentflag = match($0, /^\/\//);
      emptycommentflag = match($0, /^\/\/$/);

      # Find empty comment begin and end
      if (commentflag != 0 && commentflag0 == 0) {
        if (emptycommentflag != 0)
          printf("%d\n", NR);
      }
      if (commentflag == 0 && commentflag0 != 0) {
        if (emptycommentflag0 != 0)
          printf("%d\n", NR - 1);
      }

      commentflag0 = commentflag;
      emptycommentflag0 = emptycommentflag;
    }' > "$t"

  # Delete the trailing empty comment newline if there is one
  local line=0
  local n=0
  while read line
  do
    ((line-=n))
    sed -i -e ''$line'{/^\/\/$/d}' "$f"
    ((n++))
  done < "$t"

  # Remove the empty file
  rm "$t"

  return $errs
}

# quotes: clean up quotes usage

quotes ()
{
  local f="$1"
  local t=$(mktemp)

  # '^> > >' ==> '^>>'
  # '^ *>Something' ==> '^ *> Something'
  # '^ >' ==> '^>'
  sed -i -e '/^[>]*\s*```/,/^[>]*\s*```/!{/^\s*>\{1,\} \{2,\}/!{/^\s*>/{/^\s*>$/!{s/^\(\s*\)>\s*>\s*/\1>> /g;s/^\(\s*\)>>\s*>\s*/\1>>> /g;s/^\(\s*\)>>>\s*>\s*/\1>>>> /g;s/^\(\s*>\{1,\}\)\s*/\1 /g;/^\s*```/,/^\s*```/!{s%^ \(>\{1,\}\)\s*\(.*\)%\1 \2%g};s/\s*$//g}}}}' "$f"

  # Backup for operation
  cp "$f" $t

  # Uniq quoted empty newlines (^>$)
  cat $t | gawk '
    BEGIN { x = 0; } {
      if ($0 != ">") {
        printf("%s\n", $0);
        x = 1;
      } else if (x == 1) {
        x = 0;
        printf("%s\n", $0);
      }
    }' > "$f"

  # Delete the begining and ending '>' if there is
  cat "$f" | \
    gawk 'BEGIN{ quotebegin=0; quoteflag0=0; emptyquoteflag=0; spaces0=0; spacesmin=0; }{
      quoteflag = match($0, /^>/);
      emptyquoteflag = match($0, /^>$/);

      # Find empty quote begin and end
      if (quoteflag != 0 && quoteflag0 == 0) {
        if (emptyquoteflag != 0)
          printf("%d\n", NR);
      }
      if (quoteflag == 0 && quoteflag0 != 0) {
        if (emptyquoteflag0 != 0)
          printf("%d\n", NR - 1);
      }

      quoteflag0 = quoteflag;
      emptyquoteflag0 = emptyquoteflag;
    }' > "$t"

  # Delete the trailing empty quote newline if there is one
  local line=0
  local n=0
  while read line
  do
    ((line-=n))
    sed -i -e ''$line'{/^>$/d}' "$f"
    ((n++))
  done < "$t"

  # Remove the empty file
  rm "$t"

  return 0
}

# translate: detect translate issue if required

is_translation ()
{
  local f="$1"

  # Check header
  grep -q -E "^> Translator:" "$f" && return 0

  # Check fixed pattern
  grep -q -E "本文为[^,、，。;；：]*的翻译" "$f" && return 0

  # Header info should be filtered
  header_lines="$(echo "$def_headers" | tr ' ' '|')"

  # Check the lines begin with: '> ', calculate pure english lines
  local total=$(grep '^ *>' "$f" | grep -v -P "$chinese" | grep -v -E "$header_lines" | wc -l)

  # Detect the pure english lines with '> ', if more than 10, it should be a translation
  [ $total -ge $lines_in_quotes ] && return 0

  # Check pure English lines, filter the urls alias, empty lines, images, lists, codes, tables
  local english_total=$(grep -v -P '[\x{4e00}-\x{9fa5}\x{3040}-\x{30FF}]' "$f" | grep -E -v "^\s*\[[a-zA-Z0-9#]{1,}\]: |^\s*$|\!\[.*\]\(|^\s*[+*0-9-]\s*|^\|\s*|^\s*$|^\s*[$#/]|$header_lines" | sed -ne '/^\s*```/,/^\s*```/!p' | wc -l)

  # Detect lines begin with Chinese
  local chinese_total=$(grep -P '^[\x{4e00}-\x{9fa5}\x{3040}-\x{30FF}]' "$f" | wc -l)

  # Detect the pure english lines, if more than 50 and it has lines begin with Chinese words, it should be a translation
  local l=$(lang "$f")
  [ $l -eq 2 -a $english_total -ge $lines_in_english -a $chinese_total -ge $lines_in_chinese ] && return 0

  return 1
}

translate ()
{
  local f="$1"
  local errs=0
  local t=$(mktemp)

  # Header info should be filtered
  header_lines="$(echo $def_headers | tr ' ' '|')"

  # The original English lines should be added with "> " prefix
  local total=$(grep '^\s*>' "$f" | grep -v -P "$chinese" | grep -v -E "$header_lines" | wc -l)
  if [ $total -lt $lines_in_quotes ]; then
    err_print "As a translation, the original articles should be added in example format"
    ((errs++))
  fi

  # Check the quotes format, it should be "> ", not ">"
  total=$(grep -v -P '[\x{4e00}-\x{9fa5}\x{3040}-\x{30FF}]' "$f" | sed -ne '/^[>]*\s*```/,/^[>]*\s*```/!p' | grep -E -v '^>>{1,}|^>>{0,}\s*[-+*]|^>>{0,}\s{2,}[^ ]' | grep -E "^\s*>[^ ]|^ >|^\s*> {2,}" | wc -l)
  if [ $total -ge 1 ]; then
    err_print "The quote format should be '> Something', not '>Something' or ' >  Something'"
    local t=$(mktemp)
    grep -v -P '[\x{4e00}-\x{9fa5}\x{3040}-\x{30FF}]' "$f" | sed -ne '/^[>]*\s*```/,/^[>]*\s*```/!p' | grep -E -v '^>>{1,}|^>>{0,}\s*[-+*]|^>>{0,}\s{2,}[^ ]' | grep -E "^\s*>[^ ]|^ >|^\s*> {2,}" | head -2 > $t
    cat_print "$t" "Please correct the whitespaces"
    rm $t
    ((errs++))
  fi

  total=$(grep -v -P '[\x{4e00}-\x{9fa5}\x{3040}-\x{30FF}]' "$f" | sed -ne '/^[>]*\s*```/,/^[>]*\s*```/!p' | grep "^\s\{2,\}>" | wc -l)
  if [ $total -ge 1 ]; then
    warn_print "Please not use spaces before '>', but the spaces may be required in some scenes, do check it."
    local t=$(mktemp)
    grep -v -P '[\x{4e00}-\x{9fa5}\x{3040}-\x{30FF}]' "$f" | sed -ne '/^[>]*\s*```/,/^[>]*\s*```/!p' | grep -v '>>\{1,\}' | grep "^\s\{2,\}> " > $t
    cat_print "$t" "Are the whitespaces before '>' necessary?"
    rm $t
  fi

  # Return for no errors
  [ $errs -eq 0 ] && return 0

  # Do what else
  exam_print "translate"

  ask_print "Do you want to fix them automatically? (y/n) "

  if [ "$answer" = "y" ]; then
    # Call quotes directly
    quotes "$f"

    # Prefix every pure English lines with '>'
    grep -n -v -P '[\x{4e00}-\x{9fa5}\x{3040}-\x{30FF}]' "$f" | sed -ne '/^[0-9]\{1,\}:[>]*\s*```/,/^[0-9]\{1,\}:[>]*\s*```/!p' | grep -v "^[0-9]\{1,\}:\s*>[^ ]" | grep -E -v '^[0-9]{1,}:(\[[a-zA-Z0-9#]{1,}\]|\s*$|\s*[$#/]|!\[.*\]\(|\s*[0-9-]\s*|\|\s*|\s{2,}>\s*$|>>{0,}\s*[-+*]|>>{0,}\s{2,}[^ ])' | cut -d ':' -f1 > $t
    for l in $(cat $t)
    do
      # If begin with more than one whitespaces, reserve them
      sed -i -e "${l}{s%^\([a-zA-Z]\{1,\}\)%> \1%g}" "$f"
    done
  else
    err_print "Please fix us the above issues manually yourself"
  fi

  # Delete the temp file
  rm $t

  return $errs
}

# misc: misc typesetting issues

misc ()
{
  return 0
}

# codeinline: Fix up inline coding format in Chinese sentence
# Example: 这里是中文中出现了`main()`函数的情况，还出现了`__label`。--> 这里是中文中出现了 `main()` 函数的情况，还出现了 `__label`。

lines_fix_begin ()
{
  local f="$1"
  lines="$2"

  for l in $lines
  do
    sed -i -e "${l}s/^/tico_lines_fix/g" "$f"
  done

  return 0
}

lines_fix_end ()
{
  local f="$1"

  sed -i -e "s/^tico_lines_fix//g" "$f"

  return 0
}

codeinline ()
{
  local f="$1"
  local t=$(mktemp)
  local errs=0

  # Wrap the codeblock without '```'
  codeblock_fix_begin "$f"

  # Check single one ` issue, if not in "`" or '`', report as an issue
  cat "$f" | tr -d -c '`\n' | gawk '{ if (length != 0) { if (length % 2 == 1) printf("sed -ne \"%d{=;p}\" %s\n", FNR, "'$f'"); } }' | bash | sed -ne "{N;s:\n:\::;p}" \
    | sed -ne '/:[>]*\s*```/,/:[>]*\s*```/!p' | grep -v '"```"' | grep -v "'\`\`\`'" | grep -v '"`"' | grep -v "'\`'"  > "$t"
  if [ -s "$t" ]; then
    err_print "Potential mismatch inline code style found"
    cat_print "$t" "Such inline code may be bad" | sed -e 's/\([^`]\)`\([^`]\)/\1\x1b[1;33m`\x1b[0m\2/g'
    ((errs++))
  fi
  local lines="$(cat "$t" | cut -d ':' -f1)"
  rm "$t"

  # Skip such lines
  lines_fix_begin "$f" "$lines"

  # For content not include the following two parts
  sed -i -e '/^\s*```/,/^\s*```/!{/^tico_lines_fix/!{/```/!{/^ *`/!{s%[ \t ]*`[ \t ]*%`%g;s%\(`[^`]*`\)% \1 %g;s%` *\([%;):,.?，。：？；、—（）〖〗【】『』「」]\)%`\1%g;s%\([(，。：？；、—（）〖〗【】『』「」]\) *`%\1`%g;s%` *$%`%g;s%\([a-zA-Z0-9]\) *\([<(\[]\)`%\1 \2`%g;s%`\([>)\]]\) *\([a-zA-Z0-9]\)%`\1 \2%g;s%^ *`%`%g;s%\*\* `%\*\*`%g;s%` \*\*%`\*\*%g;s%`  `%`, `%g;s%` *\]%`]%g;s%\[ *`%[`%g;}}}}' "$f"

  # For line begin with '^ *`', it is a little complex
  # h: record the line into the `hold buffer`
  # s/\(^ *\)`.*/\1/g: get the begin whitespaces into `pattern buffer`
  # x: switch hold buffer with the pattern buffer, record the begin whitespaces
  # do whatever like above
  # H: append new pattern buffer into hold buffer
  # x: switch hold buffer back as pattern buffer
  # s:\n:: strip the newline and therefore merge the begin whitespaces back
  sed -i -e '/^\s*```/,/^\s*```/!{/```/!{/^tico_lines_fix/!{/^ *`/{h;s/\(^ *\)`.*/\1/g;x;s%[ \t ]*`[ \t ]*%`%g;s%\(`[^`]*`\)% \1 %g;s%` *\([%;)>:,.?，。：？；、—（）〖〗【】『』「」]\)%`\1%g;s% *\([(<，。：？；、—（）〖〗【】『』「」]\) *`%\1`%g;s%` *$%`%g;s%\([a-zA-Z0-9]\) *\([<(\[]\)`%\1 \2`%g;s%`\([>)\]]\) *\([a-zA-Z0-9]\)%`\1 \2%g;s%` *$%`%g;s%^ *`%`%g;s%\*\* `%\*\*`%g;s%` \*\*%`\*\*%g;s%`  `%`, `%g;s%`,`%`, `%g;s%` *\]%`]%g;s%\[ *`%[`%g;H;x;s:\n::}}}}' "$f"

  # Restore such lines
  lines_fix_end "$f"

  # Unwrap the codeblocks without '```'
  codeblock_fix_end "$f"

  # For tables who have the style like "`code` |", or "| `code`"
  grep -q -E '\|\s*`|`\s*\|' "$f"
  if [ $? -eq 0 ]; then
    has_tables "$f"
    if [ $? -eq 0 ]; then
      tables "$f"
      tables=0
    fi
  fi

  return $errs
}

# codeblock: detect and correct codeblock issues

codeblock ()
{
  local f="$1"
  local errs=0
  local total=0

  # Codeblock should be wrapped with two '```', one begin, one end, sometimes, first one is like '```shell' and no whitespace after '```'
  # clean up all of the '```', no whitespace in and no whitespace trailing
  sed -i -e 's%^\( *\)` *` *` *%\1```%g' "$f"
  sed -i -e '/^\( *\)```/s%[[:space:]]*$%%g' "$f"

  # Detect mismatch of '```', the total number should be 2*n, ignore the ones quoted with '>'
  total=$(grep '^\s*```' "$f" | wc -l)
  ((total%=2))
  if [ "$total" -ne 0 ]; then
    err_print "Potential mismatch codeblock style found"

    grep -n '^\s*```' -A1 -B1 "$f" > "$t"
    cat_print "$t" "Current codeblocks" | sed -e 's/\([0-9]*: *\)\(```\)/\1\x1b[1;33m\2\x1b[0m/g'
    ((errs++))

    return $errs
  fi

  # Add newline before and after '```'
  local t=$(mktemp)
  cat "$f" | \
    gawk 'BEGIN{ total=0; }{
      codeblockflag = match($0, /^\s*```/);

      # Find codeblock begin
      if (codeblockflag != 0) {
        if (total % 2 == 0)
          codeblockbegin=NR;
        else {
          codeblockend=NR;
          printf("%d %d\n", codeblockbegin, codeblockend);
          codeblockbegin=0
          codeblockend=0
        }
        total ++;
      }
    }' > "$t"

  local begin=0
  local end=0
  local n=0
  while read begin end
  do
    ((begin+=n))
    ((end+=n))
    sed -i -e ''${begin}'i\\n' "$f"
    ((end+=2))
    sed -i -e ''${end}'a\\n' "$f"
    ((n+=4))
  done < "$t"

  # Uniq the new added newlines
  newline "$f"
  rm "$t"

  return $errs
}

# tables: format markdown tables
# Reference: Use a cli from https://github.com/darkriszty/MarkdownTablePrettify-VSCodeExt

has_tables ()
{
  local f="$1"
  local total=0

  codeblock_fix_begin "$f"

  # tables must have '|'
  sed -ne '/^\s*```/,/^\s*```/!p' "$f" | grep -q '|'
  if [ $? -ne 0 ]; then
    codeblock_fix_end "$f"
    return 1
  fi

  # tables must have '-|-' like format
  total=$(sed -ne '/^\s*```/,/^\s*```/!p' "$f" | sed -ne '/-[ \t:]*|[ \t:]*-/{s%-[ \t:]*|[ \t:]*-%\n&\n%gp}' | grep '\-[ \t:]*|[ \t:]*\-' | wc -l)
  if [ $total -lt 1 ]; then
    codeblock_fix_end "$f"
    return 1
  fi

  # tables must has three lines of '|'
  total=$(sed -ne '/^\s*```/,/^\s*```/!p' "$f" | grep '|' | tr -d -c '|\n' | wc -l)
  if [ $total -lt 3 ]; then
    codeblock_fix_end "$f"
    return 1
  fi

  codeblock_fix_end "$f"

  return 0
}

tables ()
{
  local f="$1"
  local t=$(mktemp)

  # If not tables, return out
  has_tables "$f"
  [ $? -ne 0 ] && return 0

  codeblock_fix_begin "$f"

  # Remove whitespaces around chinese symbols
  sed -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/{s%\s*\([，。：？；、（）〖〗【】『』「」]\)\s*%\1%g;s%^\(\s*\)\([-+*]\|[0-9]\{1,\}\.\)\([，。：？；、（）〖〗【】『』「」]\)%\1\2 \3%g}}' "$f"

  # Merge multiple whitespaces to one in english content
  sed -E -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/{s%([-.,;:+*_~)>]) \s*%\1 %g;s%([a-zA-Z]) \s*([a-zA-Z])%\1 \2%g;}}' "$f"
  sed -E -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/{s%\s*([,;]|\.[^.a-zA-Z]|\.$|:[^=])%\1%g;}}' "$f"

  codeblock_fix_end "$f"

  # Add missing begin '|' for tables, Get the spaces at first
  cat "$f" | sed -e 's%^ *$%%g;s%\t%    %g' | \
    gawk 'BEGIN{ tablebegin=0; tableflag0=0; spaces0=0; spacesmin=0; }{
      tableflag = match($0, /\|/);
      tableflag_main = match($0, /--[: ]*\|[: ]*--/);
      spaces = match($0, /[^ ]/);
      spaces--;

      # Find table begin
      if (tableflag_main != 0 && tableflag0 != 0) {
          tablebegin = NR - 1;
          if (spaces < spaces0)
            spacesmin=spaces;
          else
            spacesmin=spaces0;
      }

      if (tableflag != 0 && tablebegin != 0 && spaces < spacesmin)
        spacesmin = spaces;

      # Find table end
      if (tableflag0 != 0 && tableflag == 0 && tablebegin != 0) {
        tableend = NR - 1;
        if (tableend - tablebegin >= 2) {
          printf("%d %d %d\n", tablebegin, tableend, spacesmin);
          tableflag_main = 0;
          tablebegin = 0;
          tableend = 0;
          spacesmin = 0;
        }
      }

      tableflag0 = tableflag;
      spaces0 = spaces;
    }' > "$t"

  # Make sure there is a '|' at the begin
  if [ -s "$t" ]; then
    while read tablebegin tableend spaces
    do
      # sed -i -e "${tablebegin},${tableend}{/^\s*|/!{s/^\(\s*\)/\1|/g}}" "$f"
      sed -i -e "${tablebegin},${tableend}{/^\s*|/!{s/^\($(symbols ' ' $spaces)\)/\1|/g}}" "$f"
    done < "$t"
  fi

  # Install markdown-table-prettify
  local mtp=$TOP_DIR/node_modules/.bin/markdown-table-prettify
  if [ ! -f "$mtp" ]; then
    $npm install markdown-table-prettify
    [ $? -ne 0 ] && err_print "Failed to install markdown-table-prettify" && return 1
  fi

  local table_format_cmd="$mtp < "$f" > "$t""
  info_print "Running [markdown-table-prettify] ..."

  eval "$table_format_cmd"

  if [ $? -eq 0 ]; then
    printf "%s\n" "$(< "$t")" > "$f"
  else
    warn_print "Failed to format tables, please try to run it manually."
  fi

  # Remove the temp file
  rm $t

  return 0
}

# to2unix: make sure it is a unix format document at first

tounix ()
{
  local f="$1"
  local errs=0
  local t=$(mktemp)

  dos2unix "$f" > $t 2>&1
  errs=$?
  cat_print "$t" "Dos2unix Log"
  rm $t

  if [ $errs -eq 0 ]; then
    local total_adds=$(git diff "$f" | grep '^+' | grep -v '^+++ b/' | wc -l)
    local total_lines=$(cat "$f" | wc -l)

    # If the whole file is changed, commit it to get a clean base for late changes
    if [ $total_adds -gt $lines_in_changes -o $total_adds -eq $total_lines ]; then
      warn_print "Commit conversion generated by dos2unix for changes too big: $total_adds"
      commit_tounix=1
    fi
  else
    err_print "Failed to convert "$f" to unix format via dos2unix"
  fi

  return $errs
}

# spaces: strip whitespaces, ^M strings at the end of the file; merge several newlines to one newline

spaces ()
{
  local f="$1"
  local t=$(mktemp)

  # Fix up some spaces: `c2 a0`, this space differs from the normal whitespace, replace it with the normal ones
  sed -i -e 's% % %g' "$f"

  # Strip ending whitespaces
  sed -i -e "s%[[:space:]]*$%%g;s%^[[:space:]]*$%%g" "$f"

  # Strip control info and color info in console recording
  cp "$f" $t

  # Allow set the number of whitespaces of a tab, by default, it is 8; if set to 0, don't expand the tabs, but use tabs instead of whitespaces
  if [ "$cols" = "1" ]; then
    if [ "$tabs" = "0" ]; then
      sed 's/\x1b\[[0-9;]*[mG]//g' $t | col -bp > "$f"
    else
      sed 's/\x1b\[[0-9;]*[mG]//g' $t | expand -i -t $tabs | col -bpx > "$f"
    fi
  else
    sed -i -e 's/\x1b\[[0-9;]*[mG]//g' "$f"
  fi

  # Strip spaces before and after "<br/>"
  sed -i -e '0,/^#/{/^> /{s%\s*[`]*<br/>[`]*\s*%<br/>%g;s%<br/><br/>%<br/>%g}}' "$f"

  # Correct the whitespaces in []()   [![]()]()
  sed -i -e 's%\[\s*\([^[(]*[^ \t]\)\s*\](\s*\([^[(]*[^ \t]\)\s*)%[\1](\2)%g' "$f"
  sed -i -e 's%\[\s*\(!\[[^[(]*\]([^[(]*[^ \t])\)\s*\](\s*\([^[(]*[^ \t]\)\s*)%[\1](\2)%g' "$f"

  # Correct the whitespaces in ![]()
  sed -i -e 's%!\[\s*\([^[(]*[^ \t]\)\s*\](\s*\([^([]*[^ \t]\)\s*)%![\1](\2)%g' "$f"

  # Correct the whitespaces in [][]   [![]()][]
  sed -i -e 's%\[\s*\([^[(]*[^ \t]\)\s*\]\[\s*\([^[(]*[^ \t]\)\s*\]%[\1][\2]%g' "$f"
  sed -i -e 's%\[\s*\(!\[[^[(]*\]([^[(]*[^ \t])\)\s*\]\[\s*\([^[(]*[^ \t]\)\s*\]%[\1][\2]%g' "$f"

  # Support the non-standard codeblocks
  codeblock_fix_begin "$f"

  # Remove whitespaces around chinese symbols
  sed -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/!{/^\s*[（〖【『「]/!{s%\s*\([，。：？；、（）〖〗【】『』「」]\)\s*%\1%g;s%^\(\s*\)\([-+*]\|[0-9]\{1,\}\.\)\([，。：？；、（）〖〗【】『』「」]\)%\1\2 \3%g}}}' "$f"

  # Only reserve one whitespaces between chinese words and english words
  vim -T ansi --noplugin -n -e -c "silent! %s/\([a-zA-Z0-9]\)[ \t][ \t]*\([\u4e00-\u9fa5\u3040-\u30FF]\)/\1 \2/g" -c 'silent! wq' "$f" > /dev/null
  vim -T ansi --noplugin -n -e -c "silent! %s/\([\u4e00-\u9fa5\u3040-\u30FF]\)[ \t][ \t]*\([a-zA-Z0-9]\)/\1 \2/g" -c 'silent! wq' "$f" > /dev/null

  # Merge multiple whitespaces to one in english content
  sed -E -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/!{/^\s*>{1,}\s{2,}/!{0,/^#/!{s%([-.,;:+*_~)>]) \s*%\1 %g;s%([a-zA-Z]) \s*([a-zA-Z])%\1 \2%g;}}}}' "$f"
  sed -E -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/!{s%\s*([,]|\.[^.a-zA-Z]|\.$|[;:][^=-\)\(])%\1%g;}}' "$f"

  # Remove whitespaces: ( xxx ) --> (xxx)
  sed -i -e '/^\s*```/,/^\s*```/!{s%(\s*\([^()]*[^ \t]\)\s*)%(\1)%g;}' "$f"

  # Make sure there is a space in list style format
  sed -i -e '/^\s*```/,/^\s*```/!{s%^\([0-9]\{1,\}\)\.\s*\([^0-9]\)%\1. \2%g}' "$f"

  # Correct the whitespaces in ** **, *** ***, **_ _**
  sed -i -e '/^\s*```/,/^\s*```/!{s%\*\*\*\s*\([^*]*[^ \t]\)\s*\*\*\*%***\1***%g}' "$f"
  sed -i -e '/^\s*```/,/^\s*```/!{s%\*\*\s*\([^*]*[^ \t]\)\s*\*\*%**\1**%g}' "$f"
  sed -i -e '/^\s*```/,/^\s*```/!{s%\*\*_\s*\([^_*]*[^ \t]\)\s*_\*\*%**_\1_**%g}' "$f"

  sed -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/!{/^\s*\*\*\*/!{s%\([^-+_*.)}>]\)\s*\*\*\*\s*\([^*]*[^ \t]\)\s*\*\*\*\s*\([^_*<([{]\)%\1***\2***\3%g;s%\([-+*_.)}]\)\s*\*\*\*\s*\([^*]*[^ \t]\)\s*\*\*\*\s*\([_*<([{]\)%\1 ***\2*** \3%g}}}' "$f"
  sed -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/!{/^\s*\*\*_/!{s%\([^-+_*.)}>]\)\s*\*\*_\s*\([^_*]*[^ \t]\)\s*_\*\*\s*\([^_*<([{]\)%\1**_\2_**\3%g;s%\([-+*_.)}]\)\s*\*\*_\s*\([^_*]*[^ \t]\)\s*_\*\*\s*\([_*<([{]\)%\1 **_\2_** \3%g}}}' "$f"

  sed -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/!{s%^\(\s*\)\*\*\*\s*\([^*]*[^ \t]\)\s*\*\*\*\s*\([^_*<([{]\)%\1***\2***\3%g;s%^\(\s*\)\*\*\*\s*\([^*]*[^ \t]\)\s*\*\*\*\s*\([_*<([{]\)%\1***\2*** \3%g}}' "$f"
  sed -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/!{s%^\(\s*\)\*\*_\s*\([^_*]*[^ \t]\)\s*_\*\*\s*\([^_*<([{]\)%\1**_\2_**\3%g;s%^\(\s*\)\*\*_\s*\([^_*]*[^ \t]\)\s*_\*\*\s*\([_*<([{]\)%\1**_\2_** \3%g}}' "$f"

  sed -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/!{s%^ \*\*\*\s*\([^*]*[^ \t]\)\s*\*\*\*\s*\([^_*<[{]\)%***\1***\2%g;s%^ \*\*\*\s*\([^*]*[^ \t]\)\s*\*\*\*\s*\([_*<[{]\)%***\1*** \2%g}}' "$f"
  sed -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/!{s%^ \*\*_\s*\([^_*]*[^ \t]\)\s*_\*\*\s*\([^_*<[{]\)%**_\1_**\2%g;s%^ \*\*_\s*\([^_*]*[^ \t]\)\s*_\*\*\s*\([_*<[{]\)%**_\1_** \2%g}}' "$f"

  # Correct the whitespaces in __ __, ___ ___, __* *__
  sed -i -e '/^\s*```/,/^\s*```/!{s%___\s*\([^_]*[^ \t]\)\s*___%___\1___%g}' "$f"
  sed -i -e '/^\s*```/,/^\s*```/!{s%__\s*\([^_]*[^ \t]\)\s*__%__\1__%g}' "$f"
  sed -i -e '/^\s*```/,/^\s*```/!{s%__\*\s*\([^_*]*[^ \t]\)\s*\*__%___*\1*__%g}' "$f"

  sed -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/!{/^\s*___/!{s%\([^-+*_.)}]\)\s*___\s*\([^_]*[^ \t]\)\s*___\s*\([^_*<([{]\)%\1___\2___\3%g;s%\([-+*_.)}]\)\s*___\s*\([^_]*[^ \t]\)\s*___\s*\([_*<([{]\)%\1 ___\2___ \3%g}}}' "$f"
  sed -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/!{/^\s*__\*/!{s%\([^-+*_.)}]\)\s*__\*\s*\([^_\*]*[^ \t]\)\s*\*__\s*\([^_*<([{]\)%\1__*\2*__\3%g;s%\([-+*_.)}]\)\s*__\*\s*\([^_\*]*[^ \t]\)\s*\*__\s*\([_*<([{]\)%\1 __*\2*__ \3%g}}}' "$f"

  sed -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/!{s%^\(\s*\)___\s*\([^_]*[^ \t]\)\s*___\s*\([^_*<([{]\)%\1___\2___\3%g;s%^\(\s*\)___\s*\([^_]*[^ \t]\)\s*___\s*\([_*<([{]\)%\1___\2___ \3%g}}' "$f"
  sed -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/!{s%^\(\s*\)__\*\s*\([^_\*]*[^ \t]\)\s*\*__\s*\([^_*<([{]\)%\1__*\2*__\3%g;s%^\(\s*\)__\*\s*\([^_\*]*[^ \t]\)\s*\*__\s*\([_*<([{]\)%\1__*\2*__ \3%g}}' "$f"

  sed -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/!{s%^ ___\s*\([^_]*[^ \t]\)\s*___\s*\([^_*<([{]\)%___\1___\2%g;s%^ __\s*\([^_]*[^ \t]\)\s*__\s*\([_*<([{]\)%__\1__ \2%g}}' "$f"
  sed -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/!{s%^ __\*\s*\([^_\*]*[^ \t]\)\s*\*__\s*\([^_*<([{]\)%__*\1*__\2%g;s%^ __\s*\([^_]*[^ \t]\)\s*__\s*\([_*<([{]\)%__\1__ \2%g}}' "$f"

  # Correct the whitespaces in ~~ ~~
  sed -i -e '/^\s*```/,/^\s*```/!{s%~~\s*\([^~]*[^ \t]\)\s*~~%~~\1~~%g}' "$f"
  sed -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/!{/^\s~~/!{s%\([^-+*.~)\]}]\)\s*~~\s*\([^~]*[^ \t]\)\s\([^~<([{]\)*~~\s*%\1~~\2~~\3%g;s%\([-+*.~)\]}]\)\s*~~\s*\([^~]*[^ \t]\)\s\([~<([{]\)*~~\s*%\1 ~~\2~~ \3%g}}}' "$f"
  sed -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/!{s%^\(\s*\)~~\s*\([^~]*[^ \t]\)\s*~~\s*\([^~<([{]\)%\1~~\2~~\3%g;s%^\(\s*\)~~\s*\([^~]*[^ \t]\)\s*~~\s*\([~<([{]\)%\1~~\2~~ \3%g}}' "$f"
  sed -i -e '/^\s*```/,/^\s*```/!{/\(^\s*|\||\s*$\)/!{s%^ ~~\s*\([^~]*[^ \t]\)\s*~~\s*\([^~<([{]\)%~~\1~~\2%g;s%^ ~~\s*\([^~]*[^ \t]\)\s*~~\s*\([~<([{]\)%~~\1~~ \2%g}}' "$f"

  codeblock_fix_end "$f"

  # Remove the temp file
  rm $t

  return 0
}

# toc: detect and report table of content

toc ()
{
  local f="$1"
  local t=$(mktemp)
  local errs=0

  # parse current toc
  sed -n -e '/^\s*```/,/^\s*```/!{/\(^#define\|^#!\|^#include\|^#ifdef\|^#else\|^#endif\)/!{/^#/p}}' "$f" > "$t"

  # Remove strong format in title
  sed -i -e '/^\s*```/,/^\s*```/!{/^#/{s%\*\*%%g}}' "$f"
  sed -i -e 's/\*\*//g' "$t"

  # Remove inline code format in title
  sed -i -e '/^\s*```/,/^\s*```/!{/^#/{s%`%%g}}' "$f"
  sed -i -e 's/`//g' "$t"

  # Remove link in toc
  sed -i -e '/^\s*```/,/^\s*```/!{/^#/{s%[：: ]*\[.*\](.*) *%%g}}' "$f"
  sed -i -e 's/[：: ]*\[.*\](.*) *//g' "$t"

  # Insert newline before and after title and sub titles
  local lines=$(sed -n -e '/^\s*```/,/^\s*```/!{/\(^#define\|^#!\|^#include\|^#ifdef\|^#else\|^#endif\)/!{/^#/=}}' "$f")
  local n=0
  for l in $lines
  do
    ((l+=n))
    sed -i -e ''${l}'i\\n' "$f"
    ((l+=2))
    sed -i -e ''${l}'a\\n' "$f"
    ((n+=4))
  done

  # Uniq newlines
  newline "$f"

  # Fix up toc whitespaces
  sed -i -e '/^\s*```/,/^\s*```/!{/^#/{s%^\(#\{1,\}\)\s*%\1 %g}}' "$f"

  # Remove ending symbols in title
  sed -i -e '/^\s*```/,/^\s*```/!{/^#/{s%[。.]$%%g}}' "$f"

  # Top title flag
  local has_top_title=0

  # Detect pandoc, first 3 lines should be title, author and date, see [markdown-lab](https://gitee.com/tinylab/markdown-lab)
  local p=0
  grep -v '^<!--' $f | sed -ne '1p' | grep -n "^%" | grep -q '^1:% '
  [ $? -eq 0 -a $pandoc -ne 0 ] && p=1

  if [ $p -eq 1 ]; then
    t0=$(mktemp)
    grep -v '^<!--' $f | sed -ne '1p' | grep -n "^%" > $t0
    grep -v '^<!--' $f | sed -ne '1,3p' | grep -n "^%" | wc -l | grep -q '^3$'
    [ $? -ne 0 ] && err_print "The first visible 3 lines of pandoc articles and slides should be in '% ' style" && ((errs++))
    rm $t0
  fi

  # There should be a top title
  grep -q "^#[^#]*$" $t
  if [ $? -ne 0 ];then
    err_print "No top title found, one should be added in '# Title' style" && ((errs++))
  else
    # The number of the top title should be unique, not necessary for pandoc, for the top title here is the second level title for pandoc
    if [ $p -ne 1 ]; then
      cat $t | tr -d -c '#\n' | grep "^#$" | wc -l | grep -q '^1$'
      [ $? -ne 0 ] && err_print "Top title in '# Title' style should be unique, but more than one found" && ((errs++))
    fi

    cat $t | tr -d -c '#\n' | grep -n "^#" | grep -q '^1:#$'
    [ $? -ne 0 ] && err_print "Top title in '# Title' style is not the first title?" && ((errs++))
  fi

  # The number of '#' should be increased one by one
  cat $t | tr -d -c '#\n' | grep -v '^$' | gawk 'BEGIN{old=0;}{ if (length) { printf("%d\n", length - old); old=length; };}' | grep -q '^[2-9]$' \
    && err_print "Article structure may be wrong, middle title or top title absence?" && ((errs++))

  # TODO: Duplicated sub titles?

  # The tree depth should not be more than 6
  cat $t | grep -q "^#\{7,\}"
  if [ $? -eq 0 ]; then
    err_print "Article structure depth should be <=6, but depth >6 found:" && ((errs++))
    cat_print $t "Current article structure is too deep" | sed -e "s/\(^${logspaces}\)\(#\{7,\}\)/\1\x1b[1;33m\2\x1b[0m/g"
    echo
  fi

  # There should be some specified titles, allow user override the default setting
  for s in $titles
  do
    grep -i -E -q "^##[0-9. ]*($s)" $t
    [ $? -ne 0 ] && err_print "No sub title found in '## $s' style" && ((errs++))
  done

  # Error report
  if [ $errs -ne 0 ]; then
    cat_print "$t" 'Current Table of Content' | sed -e "s/\(^${logspaces}\)\(#\{1,\}\)/\1\x1b[1;33m\2\x1b[0m/g"
    exam_print "toc"
  fi

  # Remove number list info in title
  local total_number_list=$(grep '^\(#\{1,\}\)\s*[0-9.,，、。]\{1,\}' "$f" | wc -l)
  local total_titles=$(cat "$t" | wc -l)
  if [ $total_number_list -ge 3 -a $total_number_list -ge $((total_titles-2)) ]; then
    err_print "Numeric ordered list detected, which is not recommended in title"
    cat_print "$t" "Current Table of Content" | sed -e "s/\(^${logspaces}#\{1,\}\)\s*\([0-9.,，、。]\{1,\}\)\s*/\1 \x1b[1;33m\2\x1b[0m /g"
    ask_print "Do you want to remove the ordered numbers ? (y/n)"
    [ "$answer" = "y" ] && sed -i -e '/^\s*```/,/^\s*```/!{s%^\(#\{1,\}\)\s*[0-9.,，、。]\{1,\}\s*%\1 %g}' "$f"
  fi

  # Fix up whitespaces
  sed -i -e '/^\s*```/,/^\s*```/!{s%^ \{0,1\}\(#\{1,\}\)\s*%\1 %g}' "$f"

  # Remove the temp file
  rm "$t"

  return $errs
}

# revisor: configure revisor name, allow user call it as a module

revisor ()
{
  local f="$1"
  local a="$2"
  local h="Revisor"

  # Use user's arguments setting
  if [ -n "$a" ]; then
    v="$a"

    ask_print "Is '$a' your revisor? (y/n)"
  fi

  # If no argument or user want to config
  if [ -z "$a" -o "$answer" != "y" ]; then
    config_revisor "$f" "$h"
    eval v="\${$h}"
  fi

  # Write down revisor user want
  local t=$(sed -ne "1,/^# /{/^> $h/=}" "$f")
  if [ -n "$t" ]; then
    sed -i -e "${t}s%^> $h:.*%> $h: $v<br\/>%g" "$f"
  else
    t=$(sed -ne "1,/^# /{/^> Date/=}" "$f")
    if [ -n "$t" ]; then
      sed -i -e "${t}a> $h: $v<br\/>" "$f"
    else
      t=$(sed -ne "1,/^# /{/^> Project/=}" "$f")
      if [ -n "$t" ]; then
        sed -i -e "${t}i> $h: $v<br\/>" "$f"
      else
        sed -i -e "1i> $h: $v<br\/>" "$f"
      fi
    fi
  fi

  return 0
}

# header: check the default header

header ()
{
  local f="$1"
  end_header="<br/>"

  local errs=0
  local h=""
  local v=""

  for h in $headers
  do
    [ "$h" = "Sponsor" ] && end_header=""

    v="$(header_value "$f" "$h")"

    if [ -z "$v" ]; then
      errs=1
      local _h=""
      local h_val=""
      eval h_val="\${$h}"
      [ -z "$h_val" ] && _h=$(echo $h | tr '[A-Z]' '[a-z]') && eval h_val="\${def_${_h}}"
      [ -z "$h_val" ] && h_val="xxx"

      err_print "No such header found in '> $h: ${h_val}${end_header}' style"
    elif [ "$end_header" != "" ]; then
      # Correct missing $end_header
      sed -ne '0,/^#/p' "$f" | grep -v '^#' | grep -q "^> *$h *:.* *$end_header *$"
      if [ $? -ne 0 ]; then
        sed -i -e "s%^> \(.*\)$h\(.*\) *%> \1$h\2$end_header%g" "$f"
      fi
    fi
  done

  if [ "$errs" != "0" ]; then
    exam_print "header"

    ask_print "Auto update the header ? (y/n)"
    if [ "$answer" = "y" ]; then
      local _headers="$(echo $headers | sed -e 's/Sponsor//g')"
      local n=1

      for h in $_headers
      do
        v="$(header_value "$f" $h | tr -d '\n')"
        if [ -z "$v" ]; then
          if [ "$h" = "Revisor" ]; then
            config_value "$f" "$h"
          fi
          eval v="\${$h}"
          # Delete the old empty setting line
          sed -i -e "0,/^#/{/^> $h\s*:\s*/d}" "$f"
          sed -i -e "${n}i> $h: $v" "$f"
        fi
        ((n++))
      done

      # Insert or update Sponsor at the end
      h="Sponsor"
      local s=$(sed -ne "1,/^##/{/^> $h/=}" "$f")
      if [ -z "$s" ]; then
        eval v="\${$h}"
        n=$(sed -ne '1,/^##/{/^> /=}' "$f" | tail -1)
        sed -i -e "${n}a> $h: $v" "$f"
      else
        v="$(header_value "$f" $h | tr -d '\n')"
        if [ -z "$v" ]; then
          eval v="\${$h}"
          sed -i -e "${s}c> $h: $v" "$f"
        fi
      fi

      # Add missing $end_header
      sed -i -e "1,/^# /{/^> /{/^> Sponsor/!{/<br\/>/!{s%\(.*\) *$%\1<br/>%g}}}}" "$f"
      errs=0
    else
      err_print "Please add the above missing header info manually."
    fi
  fi

  # Convert all of the tables to whitespaces
  sed -i -e '0,/^#/{/^> /{s/\t/        /g}}' "$f"

  # Fix up potential '：' in header
  sed -i -e '0,/^#/{/^> /{s/：/:/g}}' "$f"

  # Strip the redudant spaces
  sed -i -e '0,/^#/{/^> /{s/: \s*/: /g}}' "$f"

  # Align the headers
  local corrector_prefix="> $def_corrector:"
  local corrector_len=$(echo -n "$corrector_prefix" | wc -c)
  local t=$(mktemp)
  sed -ne '0,/^#/{/^> /{/:/{p;=}}}' "$f" | sed -e 'N;s:\n: :' | gawk 'BEGIN{ m=0; }{ x=match($0,/:[^:]*/); y=gensub(/.* ([0-9]*)$/,"\\1","g"); printf("%d %d\n",x,y); if (x > m) m=x; }END{ printf("%d\n",m); }' > "$t"

  local max_len=$(tail -1 "$t")
  [ $max_len -lt $corrector_len ] && max_len=$corrector_len

  sed -i -e '$d' "$t"
  ((max_len++))

  local len=0
  local line=0
  local spaces=""
  while read len line
  do
    spaces="$(symbols ' ' $((max_len - len)))"
    sed -i -e "${line}s/^\(>[^:]*\):\s*/\1:${spaces}/g" "$f"
  done < "$t"
  rm "$t"

  return $errs
}

# filename: check the default file name

filename ()
{
  local f="$1"
  local b="$(basename "$f")"

  echo "$b" | grep -q -E '^[0-9]{8,8}[-_.a-z0-9]*.md|^README.md'

  if [ $? -ne 0 ]; then
    echo "$b" | grep --color=auto --label "${logspaces}${b}" -H ' '
    [ $? -eq 0 ] && err_print "Whitespaces detected in filename, which is not allowed"

    echo "$b" | grep --color=auto --label "${logspaces}${b}" -H '[A-Z]'
    [ $? -eq 0 ] && err_print "Capital letters [A-Z] detected in filename, which is not allowed"

    echo "$b" | sed -ne 's/^\([0-9]*\).*/\1/gp' | tr -d -c '[0-9]' | wc -c | grep -q '^8$'
    if [ $? -ne 0 ]; then
      echo "$b" | grep --color=auto --label "${logspaces}${b}" -H '^[0-9]\{1,\}'
      err_print "Wrong date string detected in filename, the normal format should be 8 numbers"
    fi

    echo "$b" | tr -d '[\-_.a-zA-Z0-9\n]' | wc -c | grep -q '^0$'
    if [ $? -ne 0 ]; then
      echo "$b" | grep --color=auto --label "${logspaces}${b}" -H '[^-_.a-z0-9]'
      err_print "Invalid characters detected in filename, the normal allowed characters: -, _, ., a-z,0-9"
    fi

    local b1="$(date +'%Y%m%d')-$(echo $b | sed -ne 's/^[0-9]*\(.*\)/\L\1/gp' | tr -d -c '[\-_.a-z0-9]' | tr '[A-Z]' '[a-z]' | sed -e 's/^-//g')"
    local f1="$(absdir "$f")/$b1"

    rename_cmd="git mv "$f" "$f1""

    ask_print "Rename it from [$b] to [$b1] ? (y/n)"
    if [ "$answer" = "y" ]; then
      if [ ! -f "$f1" ]; then
        $rename_cmd
      else
        ask_print "There was a [$b1] file, override it and remove the old [$b] ? (y/n)"

        local f_bak="$f1.bak.$(date +'%Y%m%d-%H%M%S')"
        info_print "Backup old "$f1" to "$f_bak""

        info_print "Move from "$f" to "$f1""
        git rm -rf "$f1" >/dev/null 2>&1 || rm -rf "$f1"

        $rename_cmd
      fi

      # Replace all of the target files
      ref_files="$(grep -m1 -H "$f" -ur "$(absdir "$f")"  | cut -d ':' -f1)"
      [ -n "$ref_files" ] && sed -i -e "s%$f%$f1%g" $ref_files

      # Commit it directly
      commit "$f $f1 $ref_files" "filename"

      # Continue correct
      exec "$0" "$f1"
    else
      warn_print "Please correct and rename: $rename_cmd"
      exam_print "filename"
    fi
    return 1
  fi

  return $errs
}

# images: should use local image instead of remote

images ()
{
  local f="$1"
  local t=$(mktemp)
  local dir="$(absdir "$f")"
  local errs=0

  # For late commit
  commit_images=""

  # Wrap the codeblock without '```'
  codeblock_fix_begin "$f"

  # Find the image_dir in article if it has
  local first_image_file="$(sed -ne '/^\s*```/,/^\s*```/!p' "$f" | grep -i '!\[.*\]([.]*[/]*images/' | sed -e 's%.*!\[[^(]*\](\([^(]*\)).*%\1%g' | sed -e 's%^\./%%g;s%^/%%g' | grep "^images/" | head -1)"
  if [ -n "$first_image_file" ]; then
    local rel_image_dir="$(dirname "$first_image_file")"
    local image_dir="$dir/$rel_image_dir"
  fi

  # Find the default image dir: riscv_module/partn is recommended if no image in currect article
  if [ -z "$rel_image_dir" -o -z "$image_dir" -o ! -d "$image_dir" ]; then
    local image_dir0="$(basename "$f" | sed -e 's/^[0-9]\{8,8\}-//g' | rev | cut -d '.' -f2- | rev | sed -e 's@-part\([0-9]*\).*@/part\1@g')"
    local image_dir1="$(echo "$image_dir0" | sed -e 's%/part.*%%g')"
    local image_dir2="$(echo "$image_dir0" | sed -e 's/-/_/g')"
    local image_dir3="$(echo "$image_dir2" | sed -e 's%/part.*%%g')"
    local top_image_dir=$dir/images
    local prefix=$def_prefix

    # Try articles/images/cpu_design/part1, cpu_design, cpu-design/part1, cpu-design, riscv_cpu_design/part1, riscv_cpu_design, riscv-cpu-design/part1, riscv-cpu-design order
    for p in "$image_dir0" "$image_dir1" "$image_dir2" "$image_dir3"
    do
      for _p in $p ${prefix}_$p ${prefix}-$p
      do
        image_dir="$top_image_dir/$_p"
        [ -d "$image_dir" ] && break
      done
      [ -d "$image_dir" ] && break
    done

    rel_image_dir="$(echo "$image_dir" | sed -e "s%$dir/%%g")"
  fi

  # Convert mermaid to png, https://github.com/mermaid-js/mermaid-cli
  grep -q '^\s*``` *mermaid' "$f"
  if [ $? -eq 0 -a "$mermaid" = "1" ]; then
    mermaid_img_name=mermaid-"$(basename "$f" | sed -e 's/^[0-9]\{8,8\}-//g' | rev | cut -d '.' -f2- | rev)"
    mkdir -p "$image_dir"

    # Allow customize the prompt info
    local mermaid_img_download_prompt="下载由 Mermaid 生成的 PNG 图片"

    # Remove the old image download link, use a range for tolerant
    local posbegin=1
    local posend=1
    local i=0
    for l in $(sed -ne '/^\s*```mermaid/,/^\s*```/{/^\s*``` *$/{=}}' "$f")
    do
      ((posbegin=l-i-2))
      ((posend=posbegin+4))
      sed -i -e "$posbegin,$posend{/\[$mermaid_img_download_prompt\]/d}" "$f"
      ((i++))
    done

    local indents=[]
    i=0
    for l in $(sed -ne '/^\s*```mermaid/,/^\s*```/{/^\s*``` *mermaid/{=}}' "$f")
    do
      ((indents[$i]=$(sed -ne "${l}p" "$f" | tr -d -c ' ' | wc -c)))
      ((i++))
    done

    # Let mermaid_cli learn all of the mermaid sources
    sed -i -e 's/^\s*``` *mermaid/```mermaid/g' "$f"

    # Install mmdc
    local mmdc=$TOP_DIR/node_modules/.bin/mmdc
    if [ ! -f "$mmdc" ]; then
      $npm install @mermaid-js/mermaid-cli
      [ $? -ne 0 ] && err_print "Failed to install @mermaid-js/mermaid-cli" && return 1
    fi

    local mermaid_mmdc_cmd="$mmdc -i "$f" -o "$image_dir/${mermaid_img_name}.png""
    info_print "Running [mermaid-cli-mmdc] ..."
    eval "$mermaid_mmdc_cmd"

    # Insert the address in the markdown file
    i=0
    j=0
    local spaces=""
    local indent=0
    local posold=1
    local posnext=1
    local pos=0
    for n in $(sed -ne '/^\s*```mermaid/,/^\s*```/{/^\s*``` *$/=}' "$f")
    do
      ((indent=${indents[$j]}))
      ((pos=n+i))
      ((i+=2))
      ((j++))

      if [ "$indent" != "0" ]; then
        spaces="$(symbols ' ' $indent)"
      else
        spaces=""
      fi

      local img_id=$(grep "$rel_image_dir/${mermaid_img_name}-${j}.png" "$f" | cut -d ':' -f1 | tr -d '[]')
      if [ -z "$img_id" ]; then
        sed -i -e ''${pos}'a\\n（'"[$mermaid_img_download_prompt]($rel_image_dir/${mermaid_img_name}-${j}.png)"'）' "$f"
      else
        sed -i -e ''${pos}'a\\n（'"[$mermaid_img_download_prompt][$img_id]"'）' "$f"
      fi

      commit_images="$commit_images $image_dir/${mermaid_img_name}-${j}.png"

      ((posnext=pos+2))

      if [ "$indent" != "0" ]; then
        # Restore the indents of the mermaid entry
        sed -i -e "${posold},${posnext}{/\`\`\`mermaid/{s/^/${spaces}/}}" "$f"
        # Align with the indents of the mermaid
        sed -i -e "${posnext}s/^/${spaces}/g" "$f"
      fi
      posold=$posnext
    done
  fi

  # Images should be added in a whole newline
  # TODO: Add newline with alignment for the images

  sed -ne '/^\s*```/,/^\s*```/!p' "$f" | grep -n -E -v '^ *!\[[^[]*\](.*)|`!\[[^[]*\](.*)`|^ *\[!\[[^[]*\](.*)\][([]' | grep -E '!\[[^[]*](.*)' > $t
  if [ $? -eq 0 ]; then
    warn_print "Images have been wrongly put together with the other content"
    cat_print "$t" "Such images should be put into new lines" | sed -e 's/!\[[^\[]*](.*)/\x1b[1;33m&\x1b[0m/g'
  fi

  # Fix up the image description missing issue
  sed -ne '/^\s*```/,/^\s*```/!p' "$f" | grep '!\[\](.*)' | sed -e 's/\(!\[[^[]*\](\)/\n\1/g;s/\(]([^(]*)\)/\1\n/g' | grep '!\[.*\](' | sed -ne 's/.*](\(.*\)).*/\1/gp' > $t

  local img_desc=""
  local img=""
  local _img=""
  while read img
  do
    if grep -q "!\[\]($img)" "$f"; then
      local d=$(delimiter "$img")

      img_desc="$(echo "$img" | tr "'" "\"" | tr '(' '"' | tr ')' '"' | cut -d '"' -f1 | sed -e 's/ *//g' | xargs -i basename {} | sed -e 's/\(.*\)?.*/\1/g' | sed -e "s${d}&${d}\\\&${d}g" | sed -e 's/%/-/g')"
      _img="$(echo $img | sed -e "s${d}&${d}\\\&${d}g" | tr "'" "\"" | tr '(' '"' | tr ')' '"' | sed -E -e '/["]/!{s/ //g};/"/{h;s/([^"]*) .*/\1 /g;s/ //g;x;s/[^"]* (".*")/\1/g;H;x;s:\n: :}')"
      sed -i -e "s${d}!\[\]($img)${d}![$img_desc]($_img)${d}g" "$f"
    fi
  done < $t

  # image must be put in articles/images/riscv_xxx/
  # auto download depends on the network, it may work on author's computer, it should be enabled by the author itself
  if [ "$download" = "1" ]; then
    sed -ne '/^\s*```/,/^\s*```/!p' "$f" | grep '!\[[^(]*\](http[s]*://.*)' | grep '!\[.*\](' | sed -ne 's/.*](\(.*\)).*/\1/gp' | sort -u > $t

    local img=""
    local img_url=""
    local net_name=""
    while read img
    do
      img_url=""
      img_desc=""
      net_name=""
      img_desc=""

      local d=$(delimiter "$img")
      img_url="$(echo "$img" | tr "'" "\"" | tr '(' '"' | tr ')' '"' | cut -d '"' -f1 | sed -e 's/ *//g')"
      img_desc="$(echo "$img" | tr "'" "\"" | tr '(' '"' | tr ')' '"' | cut -d '"' -f2)"
      img_name="$(echo "$img_url" | xargs -i basename {} | sed -e 's/\(.*\)?.*/\1/g' | sed -e 's/%/-/g')"
      net_name="$(echo "$img" | tr -s '/' | cut -d '/' -f2)"
      [ ! -d "$image_dir" ] && mkdir -p "$image_dir"

      wget -c "$img_url" -O "$image_dir/$img_name"
      if [ $? -eq 0 ]; then
        target_img_path="$rel_image_dir/$img_name"
        local img_info=""
        if [ -z "$img_desc" -o "$img_desc" = "$img_url" ]; then
          img_info="$target_img_path"
        else
          img_info="$target_img_path \"$img_desc\""
        fi
        sed -i -e "s${d}!\[.*\]($img)${d}![$img_name]($img_info)${d}g" "$f"

        # insert picture source site
        grep -q "^\s*\!\[$img_name\]($img_info)\s*$" "$f"
        if [ $? -eq 0 ]; then
          local ns=$(grep -n "^\s*!\[$img_name\]($img_info) *$" "$f" | cut -d ':' -f1)
          for n in $ns
          do
            local spaces="$(sed -ne "${n}s${d}^\(\s*\)!\[$img_name\]($img_info) *\$${d}\1${d}gp" "$f")"
            ((n++))
            sed -i -e "${n}{/（图片来自 $net_name）/d}" "$f"
            sed -i -e ''${n}'i（图片来自 '$net_name'）' "$f"
            sed -i -e "${n}s/^/${spaces}/g" "$f"
          done
        fi
        grep -q "[^ \t]\{1,\}!\[$img_name\]($img_info)\s*$" "$f"
        if [ $? -eq 0 ]; then
          sed -i -e "s${d}^\([^ ]*\)\(!\[$img_name\]($img_info)\)\(（图片来自 $net_name）\)*\(.*\)${d}\1\2（图片来自 $net_name）\4${d}g" "$f"
        fi
        commit_images="$commit_images $image_dir/$img_name"
      fi
    done < $t
  else
    sed -ne '/^\s*```/,/^\s*```/!{/!\[[^(]*\](http[s]*:\/\/.*)/{=;p}}' "$f" | sed -e 'N;s:\n:\::' > "$t"
    if [ -s "$t" ]; then
      err_print 'Please download the images automatically with 'download=1' variable'
      cat_print "$t" "Such images are stored in remote server" | sed -e 's%!\[[^(]*\](http[s]*://.*)%\x1b[1;33m&\x1b[0m%g'
      ((errs++))
    fi
  fi

  # print examples
  [ $errs -ne 0 ] && exam_print "images"

  # Remove the temp file
  rm $t

  # Unwrap the codeblocks without '```'
  codeblock_fix_end "$f"

  return $errs
}

# urls: should be used with alias list in the end of the file

urls ()
{
  local f="$1"
  local t=$(mktemp)
  local dir="$(absdir "$f")"
  local errs=0

  # Wrap the codeblock without '```'
  codeblock_fix_begin "$f"

  # Format the alias mapping
  sed -i -e '/^\s*```/,/^\s*```/!{/^\[[a-zA-Z0-9#]\{1,\}\]: */{s/\(^\[[a-zA-Z0-9#]\{1,\}\]:\) *\(.*\) *$/\1 \2/g;s/\(^\[[a-zA-Z0-9#]\{1,\}\]:\) *\.\/\(.*\) *$/\1 \2/g}}' "$f"

  # Clean up local file references, remove ',/' prefix
  sed -i -e '/^\s*```/,/^\s*```/!{/\[.*\](\.\//{s/\[\s*\(.*[^ \t]\)\s*\](\.\//[\1](/g;s%\s*$%%g}}' "$f"

  # Fix up wrong alias usage
  sed -ne '/^\s*```/,/^\s*```/!p' "$f" | grep -v '^ *> *' | grep '\[.*\](' | sed -e 's/\([~]*[!]*\[[^\[]*\](\)/\n\1/g;s/\(\]([^(]*)[~]*\)/\1\n/g' | grep -E -v '^!|^~~' | sed -ne 's/.*](\([0-9]*\)).*/\1/gp' | sort -u -g > $t

  while read number
  do
    grep -q "^\[$number\]: " "$f" && sed -i -e "s%\(\[.*\]\)($number)%\1[$number]%g" "$f"
  done < $t

  # Convert such cases [][http://] to [](http://); [][/path/to/] to [](/path/to/)
  sed -ne '/^\s*```/,/^\s*```/!p' "$f" | grep -v '^ *> *' | grep '\[.*\]\[' | sed -e 's/\([~]*[!]*\[[^\[]*\]\[\)/\n\1/g;s/\(\]\[[^\[]*\][~]*\)/\1\n/g'  | grep -E -v '^!|^~~' | sed -ne 's%.*\]\[\(http[s]*://.*\|.*/.*\)\].*%\1%gp' | sort -u -g > $t

  local url=""
  local _url=""
  while read url
  do
    # Ignore the urls if they are really alias, otherwise, replace them to '[]()' format.
    grep -q "^\[$url\]: " "$f" && continue

    local d=$(delimiter "$url")

    _url="$(echo $url | sed -e "s${d}&${d}\\\&${d}g" | tr "'" "\"" | tr '(' '"' | tr ')' '"' | sed -E -e '/["]/!{s/ //g};/"/{h;s/([^"]*) .*/\1 /g;s/ //g;x;s/[^"]* (".*")/\1/g;H;x;s:\n: :}')"
    sed -i -e "s%\[\s*\(.*[^ \t]\)\s*\]\[$url\]%[\1]($_url)%g;s%\s*$%%g" "$f"
  done < $t

  # Detect invalid local references
  sed -ne '/^\s*```/,/^\s*```/!p' "$f" | grep -v '^ *> *' | grep '\[.*\]\[' | sed -e 's/\([~]*[!]*\[[^\[]*\]\[\)/\n\1/g;s/\(\]\[[^\[]*\][~]*\)/\1\n/g'  | grep -E -v '^!|^~~' | sed -ne 's%.*\]\[\(.*\)\].*%\1%gp' | sort -u -g > $t

  local url=""
  local t0=$(mktemp)
  local merrs=0
  while read url
  do
    grep -q "^\[$url\]:" "$f" && continue

    sed -ne "/\[.*\]\[$url\]/{=;s/\[.*\]\[$url\]/&/gp}" "$f" | sed -e 'N;s%\n%:%' >> $t0
    ((merrs++))
  done < $t
  ((errs += merrs))

  if [ $merrs -ne 0 ]; then
    err_print "Some local references can not be found in the urls alias map"
    cat_print "$t0" "Such references may be invalid" | sed -e 's/\(\[.*\]\[.*\]\)/\x1b[1;33m\1\x1b[0m/g'
  fi
  rm $t0

  # Detect current numbering of the alias
  line=$(grep "^\[[a-zA-Z0-9#]\{1,\}\]: " "$f" | cut -d':' -f1 | tr -d '[]' | tr -d -c '[0-9\n]' | sort -u -g | tail -1 | sed -e 's/^0\{1,\}\([1-9]\{1,\}\)/\1/g')
  [ -z "$line" ] && line=0

  # Use alias instead of direct url
  sed -ne '/^\s*```/,/^\s*```/!p' "$f" | grep -v '^ *> *' | grep '\[.*\](' | sed -e 's/\([!]*\[[^\[]*\](\)/\n\1/g;s/\(]([^(]*)\)/\1\n/g' | grep -v '^!' | sed -ne 's/.*](\(.*\)).*/\1/gp' \
    | grep -E -v '^&|^#|^[a-zA-Z0-9#]*$|^[[:space:]]*$' | sort -u \
    | gawk '{ printf("%03d %s\n", '$line' + FNR, $0); }' | sed -e 's/^[[:space:]]*\([0-9]*\)[[:space:]]*/\1 /g' > $t

  local first_one=1
  local number=0
  local url=""
  local _url=""
  while read number url
  do
    line=$(grep "^\[[a-zA-Z0-9#]\{1,\}\]: $url$" "$f" | cut -d':' -f1 | tr -d '[]')
    if [ -n "$line" ]; then
      url="$(echo $url | sed -e 's@%@\\\%@g')"
      sed -i -e "s%\[\s*\(.*[^ \t]\)\s*\]($url)%[\1][$line]%g;s%\s*$%%g" "$f"
    else
      local line=$number
      if [ $first_one -eq 1 ]; then
        # Strip all trailing newlines
        newline "$f"

        # Only append a newline if no alias map exists
        tail -1 "$f" | grep -q "^\[.*\]: "
        [ $? -ne 0 ] && echo >> "$f"

        first_one=0
      fi

      # Fix up indirect link targets, tested zhihu, gitee.com
      _url="$(echo "$url" | sed -e 's,http[s]*://.*/\(link\)*?target=\(http[s]*\),\2,g;s,%3A,:,g;s,%2F,/,g;s;%3F;?;g;s,%3D,=,g;s,%26,\&,g;s,%2B,+,g;s,%25,%,')"

      echo "[$number]: $_url" >> "$f"
      url="$(echo $url | sed -e 's@%@\\\%@g')"
      sed -i -e "s%\[\s*\(.*[^ \t]\)\s*\]($url)%[\1][$line]%g;s%\s*$%%g" "$f"
    fi
  done < $t

  # Check urls availability (the remote may be blocked by GFW, images should be downloaded, but what about the web pages?)
  # Check local existence at first
  sed -ne '/^\s*```/,/^\s*```/!p' "$f" | grep '\[[^(]*\](.*)' | sed -ne 's/.*\]\((.*)[`]*\).*/\1/gp' | \
    sed -ne '/`/!{s/(\(.*\)).*/\1/gp}' | grep -E -v '^#|^[a-zA-Z0-9#]*$|^[[:space:]]*$|^mailto:' | cut -d '#' -f1 | sort -u > $t
  sed -ne '/^\s*```/,/^\s*```/!p' "$f" | grep "^\[[a-zA-Z0-9#]\{1,\}\]: " | cut -d':' -f2- | sed -e 's/^ //g' | \
    grep -E -v '^&|^#|^[a-zA-Z0-9#]*$|^[[:space:]]*$|^mailto:' | cut -d '#' -f1 | sort -u >> $t

  local url_filters_rege="($(echo "$url_filters" | tr ' ' '|'))"

  local t0=$(mktemp)
  mv $t $t0
  if [ "$url_filter" != "0" ]; then
    cat $t0 | grep -E -v "$url_filters_rege" | sort -u > $t
  else
    cat $t0 | sort -u > $t
  fi
  rm $t0

  local curl_connect_timeout=5
  if [ -n "$url_timeout" -a "$url_timeout" != "0" ]; then
    url_timeout=$(echo $url_timeout | tr -d -c '[0-9]')
    if [ -n "$url_timeout" -a "$url_timeout" != "$curl_connect_timeout" ]; then
      curl_connect_timeout=$url_timeout
    fi
  fi
  local curl_timeout="--connect-timeout $curl_connect_timeout"

  if [ -n "$url_max_timeout" -a "$url_max_timeout" != "0" ]; then
    url_max_timeout=$(echo $url_max_timeout | tr -d -c '[0-9]')
    if [ -n "$url_max_timeout" -a -n "$url_timeout" ]; then
      if [ $url_max_timeout -lt $url_timeout ]; then
        url_max_timeout=$url_timeout
      fi
    fi
    curl_timeout="$curl_timeout  --max-time $url_max_timeout"
  fi

  # Calculate '://' lines
  local remote_urls_max=5
  local remote_urls_total=$(grep "://" $t | wc -l)
  [ -z "$remote_urls_total" ] && remote_urls_total=0

  env | grep -q ^url_timeout=
  if [ "$?" -ne 0 -a $remote_urls_total -gt $remote_urls_max ]; then
    warn_print "Auto disable remote url detection for total > $remote_urls_max, reenable it with 'url_timeout=5'"
    url_timeout_autodisable=1
  fi

  # The path of the target file
  local aerrs=0
  local cerrs=0
  local nerrs=0
  local t1=$(mktemp)
  local t2=$(mktemp)
  local t3=$(mktemp)
  while read url
  do
    _url="$(echo "$url" | tr "'" "\"" | tr '(' '"' | tr ')' '"' | cut -d '"' -f1 | sed -e 's/ *//g')"
    url="$_url"

    if echo "$url" | grep -q "^/"; then
      # info_print "Checking local file: '$url' ..."
      [ ! -s $t1 ] && echo -e "$dir:\n" > $t1
      echo $url >> $t1
      ((aerrs++))
      continue
    fi
    if echo "$url" | grep -q "://"; then
      # allow disable url timeout check by url_timeout=0
      [ "$url_timeout" = "0" -o "$url_timeout_autodisable" = "1" ] && continue

      info_print "Checking remote url: '$url' ..."

      curl -I -s $curl_timeout "$url" >/dev/null
      if [ $? -ne 0 ]; then
        # warn_print "Can not access '$url' with '$curl_timeout', please check it."
        echo $url >> $t2
        ((cerrs++))
      fi
      # wait for a while to launch another test
      sleep 0.5
      continue
    fi

    # Local files
    local full_url="$dir/$url"
    if [ ! -f "$full_url" ]; then
      # info_print "Checking local file: '$url' ..."
      [ ! -s $t3 ] && echo -e "$dir:\n" > $t3
      echo $url >> $t3
      ((nerrs++))
    fi
  done < $t

  # Report them
  [ $aerrs -ne 0 ] && cat_print "$t1" "Such files address should be changed relative to target place"
  [ $cerrs -ne 0 ] && cat_print "$t2" "Such urls connect timeout with '$curl_timeout'"
  [ $nerrs -ne 0 ] && cat_print "$t3" "Such files are not in target places"
  rm $t1 $t2 $t3

  # Remove the temp file
  rm $t

  # Fails instead of just report
  ((errs += (aerrs + nerrs)))
  if [ $errs -ne 0 ]; then
    err_print "Please check the above issues and fix up them with the help from examples"
    exam_print "urls"
  fi

  # Remove the newlines between url alias
  newline "$f"
  sed -i -e '/^\s*```/,/^\s*```/!{/^\[[a-zA-Z0-9#]\{1,\}\]: /{n;/^$/d;n;/^$/d}}' "$f"
  sed -i -e '/^\s*```/,/^\s*```/!{/^\[[a-zA-Z0-9#]\{1,\}\]: /{n;/^\[[a-zA-Z0-9#]\{1,\}\]: /!{s:^\(\n\)*:\n:};n;/^\[[a-zA-Z0-9#]\{1,\}\]: /!{s:^\(\n\)*:\n:}}}' "$f"

  # Unwrap the codeblocks without '```'
  codeblock_fix_end "$f"

  return $errs
}

# refs: detect and update/add references
refs ()
{
  local f="$1"
  local t=$(mktemp)
  local r=$(mktemp)
  local l=$(mktemp)
  local errs=0

  # skip refs for generic using
  if [ -z "$titles" ]; then
    return 0
  fi

  # The ending references should be list style
  local ref=""
  for s in $titles
  do
    echo "$s" | grep -q -i 'References'
    if [ $? -eq 0 ]; then
      grep -i -E -q "^##[0-9. ]*($s)" $f
      if [ $? -eq 0 ]; then
        ref="$s"
        break
      fi
    fi
  done

  listed=""
  if [ -n "$ref" ]; then
    sed -E -n -e "/^##[0-9. ]*($ref)/,\${/^\s*\$/!{p}}" "$f" | grep -E -v "^\[[a-zA-Z0-9#]{1,}\]: |^##"  | grep -E -q '^\s*([-+*]|[0-9]*\.)'
    if [ $? -ne 0 ]; then
      err_print "References found, but not in list style"
      sed -E -n -e "/^##[0-9. ]*($ref)/,\${/^\s*$/!{=;p}}" "$f" | sed -e 'N;s%\n%:%' | grep -E -v "^[0-9]+:\[[a-zA-Z0-9#]{1,}\]: |^[0-9]+:##" > "$t"
      cat_print "$t" "References found, but not in list style:"

      ask_print "Do you want to correct them automatically? (y/n) "
      if [ "$answer" = "y" ]; then
        local lines=$(cat "$t" | cut -d ':' -f1)
        local l=0
        for l in $lines
        do
          sed -i -e "${l}s%^%- %g" "$f"
        done
      else
        ((errs++))
      fi
    else
      # unify the list style
      # Get already listed
      sed -E -n -e "/^##[0-9. ]*($ref)/,\${/^\s*$/!{=;p}}" "$f" | sed -e 'N;s%\n%:%' | grep -E -v "^[0-9]+:\[[a-zA-Z0-9#]{1,}\]: |^[0-9]+:##" > "$t"
      listed=$(cat "$t" | sed 's/.*\]\[\([0-9]*\)\]$/\1/g')

      # Fix up old refs
      local lines=$(cat "$t" | cut -d ':' -f1)
      local l=0
      for l in $lines
      do
        sed -i -e "${l}s%^\* %- %g" "$f"
      done
    fi

    # Drop old refs
    local from=$(cat "$t" | cut -d ':' -f1 | head -1)
    local to=$(cat "$t" | cut -d ':' -f1 | tail -1)
    sed -i -e "${from},${to}d" "$f"

    # rename from $t to $l
    cp $t $l
  fi

  # Generate refs from article content
  local url_ref=0
  sed -ne '/^\s*```/,/^\s*```/!{/^\[[a-zA-Z0-9#]\{1,\}\]: /p}' "$f" | sort -t '[' -g -k 2 | uniq | sed -e 's%^\[\([a-zA-Z0-9#]\{1,\}\)\]: %\1 %g' > $t
  echo -n > $r
  while read number url
  do
    # Ignore images, mailto urls
    echo $url | grep -Eq '^images/|^mailto:|.png$|.jpg$|.svg$|.gif$'
    [ $? -eq 0 ] && continue

    # set flag
    url_ref=1

    # Filter out the already listed
    echo $listed | tr ' ' '\n' | grep -wq $number
    [ $? -eq 0 ] && continue

    # Get title for local files
    echo $url | grep -Eq -v '://'
    if [ $? -eq 0 ]; then
        name="$(echo $url | sed -e 's/^[0-9]*-//g;s/.md.*//g' | tr '-' ' ')"
        dir=$(dirname "$f")
        x="$(grep -m1 '^# ' "$dir/$url" | sed -e 's/^# //g')"
        [ -n "$x" ] && name="$x"
    else
        # Get title for remote urls (from the url is fast, from remote is slow, when there are from githubs)
        name="$(echo $url | sed -e 's%/$%%g;s%\.htm[l]$%%g;s%#.*%%g;s%http[s]://[^/]*/%%g')"

        local url_filters_rege="($(echo "$url_filters" | tr ' ' '|'))"
	echo $url | grep -q "$url_filters_rege"
	if [ $? -eq 0 ]; then
          echo "LOG: Getting title of $url ..."
          x="$(wget --quiet -O - "$url" 2>/dev/null | sed -n -e 's!.*<title>\(.*\)</title>.*!\1!p' | sed -e 's/[，。；].*//g')"
          [ -n "$x" ] && name="$x"
	fi
    fi

    echo - [$name][$number] >> $r
  done < $t

  # Merge them and insert back
  local url_map=1
  from=$(grep -n "^\[[a-zA-Z0-9#]\{1,\}\]: " "$f" | head -1 | cut -d ':' -f1)
  if [ -z "$from" -o "$from" = "1" ]; then
    url_map=0
    from=$(wc -l "$f" | cut -d ' ' -f1)
  fi

  # empty the tmp file
  rm $t

  if [ -n "$ref" ]; then
    if [ $url_map -eq 1 ]; then
      ((from-=2))
    fi
    echo -e -n "" > $t
    # Merge them
    cat "$r" "$l" | sed -E -e 's/^[0-9]*:(-|\*|[0-9]*\.) */- /g'  | sort -t '[' -k3 >> $t
  else
    if [ $url_map -eq 1 ]; then
      ((from--))
    else
      echo >> $t
    fi
    echo "## 参考资料" >> $t
    local ref_total=$(cat "$r" "$l" | wc -l)
    if [ $ref_total -ne 0 ]; then
      echo >> $t
    fi
    # Merge them
    cat "$r" "$l" | sed -E -e 's/^[0-9]*:(-|\*|[0-9]*\.) */- /g'  | sort -t '[' -k3 >> $t
    if [ $url_map -eq 1 ]; then
      echo >> $t
    fi
  fi

  # Insert back
  sed -i "${from}r $t" $f

  # remove tmp files
  rm $t $r $l

  return $errs
}

# words: detect and correct the words, include Chinese and English

pycorrector ()
{
  local f="$1"
  local l=$(lang "$f")

  # Basic words detection and correction
  PYCORRECTOR_DIR=$TOP_DIR/3rd-party/words-pycorrector
  PYCORRECTOR_FILE=$PYCORRECTOR_DIR/correct.py
  PYCORRECTOR_MODEL="$PYCORRECTOR_DIR/people2014corpus_chars.klm"

  if [ -f "$PYCORRECTOR_MODEL" ]; then
    # ref: https://pan.baidu.com/s/1I2GElyHy_MAdek3YaziFYw, o5e9
    m="$PYCORRECTOR_MODEL"
  fi

  # Download if missing
  python3 -c 'import pycorrector'
  if [ $? -ne 0 ]; then
    pip3 install pycorrector
    pip3 install https://github.com/kpu/kenlm/archive/master.zip
  else
    # Fix up encoding issue
    PYCORRECTOR_INST=$(python3 -c 'import pycorrector; print(pycorrector.__path__[0])')
    if [ $lang -ne 0 -a -f $PYCORRECTOR_INST/en_spell.py ]; then
      sed -i -e 's%json.loads(f.read(), encoding="utf-8")%json.loads(f.read())%g' $PYCORRECTOR_INST/en_spell.py
    fi
  fi

  # Filter some contents
  local t=$(mktemp)
  cp "$f" "$t"
  codeblock_fix_begin "$t"
  sed -E -i -e "s/^\s*\[[a-zA-Z0-9#]{1,}\]: .*$|^\s*$|[!]*\[.*\](.*)|^\s*[+*0-9-]\s*.*|.*\s*\|\s*.*|^\s*$|^\s*[$#/].*//g" "$t"
  sed -i -e '/^\s*```/,/^\s*```/{s/^.*$//g}' "$t"
  codeblock_fix_end "$t"

  # Fix up words issue with pycorrector model
  echo
  python3 $PYCORRECTOR_FILE "$t" $l "$m"
  echo

  # Remove the temp file
  rm "$t"

  return 0
}

xmnlp ()
{
  local f="$1"

  # Basic words detection, only chinese
  XMNLP_DIR=$TOP_DIR/3rd-party/words-xmnlp
  XMNLP_FILE=$XMNLP_DIR/check.py
  XMNLP_MODEL="$XMNLP_DIR/xmnlp-onnx-models"

  # Download if missing
  python3 -c 'import xmnlp'
  if [ $? -ne 0 ]; then
    pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -U xmnlp
  fi

  local ref="https://pan.baidu.com/s/1YBqD-L5spNg0VOPSDPN3iA, l9id"
  if [ -d "$XMNLP_MODEL" ]; then
    m="$XMNLP_MODEL"
  else
    err_print "xmnlp model mmissing, please download one from: $ref and decompress it to $XMNLP_MODEL"
    return 1
  fi

  local t=$(mktemp)
  cp "$f" "$t"
  codeblock_fix_begin "$t"
  sed -E -i -e "s/^\s*\[[a-zA-Z0-9#]{1,}\]: .*$|^\s*$|[!]*\[.*\](.*)|^\s*[+*0-9-]\s*.*|.*\s*\|\s*.*|^\s*$|^\s*[$#/].*//g" "$t"
  sed -i -e '/^\s*```/,/^\s*```/{s/^.*$//g}' "$t"
  codeblock_fix_end "$t"

  # Fix up words issue with xmnlp model
  echo
  python3 $XMNLP_FILE "$t" "$m"
  echo

  # Remove the temp file
  rm "$t"

  return 0
}

# ------------------------------
# Main top functions
# ------------------------------

# Usage
usage ()
{
  local f="$1"

  [ -z "$f" ] && info_print "Usage: ${tool_raw} file.md" && return 1
  [ ! -f "$f" ] && info_print "No such file: $f" && return 1

  return 0
}

# Required tools detection

node_fix ()
{
  # mermaid require node@14
  local node_version=$(node --version)
  local node_minver="14"
  if [ "$(echo -e "$node_version\nv$node_minver" | sort -V | tail -1)" = "v$node_minver" ]; then
    err_print "Require Node version v$node_minver, but current version is $node_version"

    which apt >/dev/null 2>&1
    if [ $? -eq 0 ]; then
      sudo apt update -y
      sudo apt -y install curl dirmngr apt-transport-https lsb-release ca-certificates
      curl -sL https://deb.nodesource.com/setup_${node_minver}.x | sudo -E bash -
      sudo apt install -y --no-install-recommends nodejs
      [ $? -eq 0 ] && return 0
    fi
    return 1
  fi

  return 0
}

tools ()
{
  local f="$1"
  local to_install=""

  for t in vim git gawk col expand pip3 python3 npm dos2unix md5sum base64
  do
    which $t >/dev/null 2>&1
    if [ $? -ne 0 ]; then
      echo "expand md5sum base64" | grep -q $t
      if [ $? -eq 0 ]; then
        to_install="$to_install coreutils"
        continue
      fi
      echo "col" | grep -q $t
      if [ $? -eq 0 ]; then
        to_install="$to_install bsdmainutils"
        continue
      fi
      echo "pip3" | grep -q $t
      if [ $? -eq 0 ]; then
        to_install="$to_install python3-pip"
        continue
      fi
      to_install="$to_install $t"
    fi
  done

  # Remove duplicated
  to_install="$(echo $to_install | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed -e 's%\s*$%%g')"

  # Install or just tell users install themselves
  if [ -n "$to_install" ]; then
    err_print "Tools not found: $to_install"

    for t in apt dnf pacman
    do
      which $t >/dev/null 2>&1
      if [ $? -eq 0 ]; then
        ask_print "Do you want to install them automatically? (y/n)"
        if [ "$answer" = "y" ]; then
         case $t in
          apt)
            sudo apt update -y
            sudo apt install -y --no-install-recommends $to_install
            [ $? -eq 0 ] && return 0
            ;;
          dnf)
            sudo dnf install -y $to_install
            [ $? -eq 0 ] && return 0
            ;;
          pacman)
            sudo pacman -Sy $to_install
            [ $? -eq 0 ] && return 0
            ;;
          *)
            ;;
         esac
        fi
      fi
    done

    err_print "Please install them manually"
    return 1
  fi

  # Update node for mermaid
  grep -q '^\s*``` *mermaid' "$f"
  if [ $? -eq 0 -a "$mermaid" != "0" ]; then
    node_fix
  fi

  return 0
}

# Default setting
default ()
{
  local f="$1"

  def_modules="tounix filename spaces newline quotes header toc comments codeblock codeinline tables images urls refs typeset"
  def_typeset="pangu autocorrect"
  available_typeset="pangu autocorrect"
  def_titles="前言|概述|引言|引子|引语|简介|背景简介|Introduction 总结|结语|小结|后记|Summary|Conclusion 推荐资料|参考资料|参考文档|References"
  def_headers="Author Date Revisor Project Sponsor"
  def_corrector="Corrector"
  def_timeout=5
  def_failstop=1
  def_correctaction=0
  def_editor=vim

  # Supported file suffixes
  def_suffixes=".md .txt"

  # Filter such urls
  def_url_filters="github.com wikipedia.com"

  # npm registry
  def_npm_registry="https://npmreg.proxy.ustclug.org/"

  # log output setting
  def_logging=0
  def_logindent=6
  def_logborder_length=60

  # Words correction support
  def_modules="$def_modules words"
  def_words="epw"
  available_words="pycorrector xmnlp epw"
  # 0: chinese, 1: english, 2: both
  def_lang=0
  def_chinese='[\x{4e00}-\x{9fa5}\x{3040}-\x{30FF}]'

  # List all available modules and abstract modules
  extra_modules="revisor"
  available_modules="$def_modules $available_typeset $available_words $extra_modules"
  abstract_modules="typeset words"

  # Translation setting, lines with '>' and lines in pure English
  def_lines_in_quotes=10
  def_lines_in_english=50
  def_lines_in_chinese=25

  # Commit changes threshold
  def_lines_in_changes=50
  def_commit=0

  # Default tabs (whitespaces in a tab)
  def_tabs=8
  def_cols=0

  # Default header info
  def_title="[Original Title](remote-url-address-of-original-article)"
  def_author="Author Name <Author Email>"
  def_translator="Translator Name <Translator Email>"
  def_revisor="Revisor Name <Revisor Email>"
  def_project="[Project Name](remote-url-address-of-the-project)"
  def_sponsor="The Sponsor of the project"

  # Default template (for RISC-V Linux Project)
  def_authors="default"
  # For default image path naming
  def_prefix="riscv"
  # For auto pandoc detection
  def_pandoc=-1
  # For translate setting
  def_translate=0

  # For remote images auto download
  def_download=0

  # For mermaid use or auto upgrade
  def_mermaid=1

  # Default examples titles, should not have the same string
  def_exam_titles="文章结构|TOC 文件头|Header 文件名|Filename 图片|Images 链接|URLs 翻译|Translate"

  return 0
}

# User input
configure ()
{
  local f="$1"

  # Allow pass words
  [ -z "$words" ] && words="$def_words"
  [ -z "$lang" ] && lang=$def_lang
  [ -z "$chinese" ] && chinese="$def_chinese"

  # Translation
  [ -z "$lines_in_quotes" ] && lines_in_quotes=$def_lines_in_quotes
  [ -z "$lines_in_english" ] && lines_in_english=$def_lines_in_english
  [ -z "$lines_in_chinese" ] && lines_in_chinese=$def_lines_in_chinese

  # Commit
  [ -z "$lines_in_changes" ] && lines_in_changes=$def_lines_in_changes
  [ -z "$commit" ] && commit=$def_commit

  # logging
  [ -z "$logging" ] && logging=$def_logging
  [ -z "$logindent" ] && logindent=$def_logindent
  [ -z "$logspaces" ] && logspaces="$(symbols ' ' $logindent)"
  [ -z "$logborder_length" ] && logborder_length=$def_logborder_length
  [ -z "$logborder_cat" ] && logborder_cat="$(symbols '~' $logborder_length)"
  [ -z "$logborder_exam" ] && logborder_exam="$(symbols '-' $logborder_length)"

  # Customize for translate
  if [ -z "$translate" ]; then
    translate=$def_translate
    is_translation "$f"
    if [ $? -eq 0 ]; then
      info_print "Detect article type, translation or normal article"
      ask_print "Is it a translation? (y/n)"
      [ "$answer" = "y" ] && translate=1
    fi
  fi

  if [ "$translate" = "1" ]; then
    def_headers="Title Author Translator Date Revisor Project Sponsor"
    def_modules="translate $def_modules"
    available_modules="translate $available_modules"
    def_titles=""
  fi

  # Use 'default' setting if not user input
  [ -z "$template" ] && template="$def_template"

  # Check template, must be named with lower case characters, _ and numbers, e.g. default, default0, default1
  if [ -n "$template" ]; then
    echo "$template" | grep -q '^[a-z][_a-z0-9]*$'
    if [ $? -ne 0 ]; then
      warn_print "Ignore invalid template argument: $template, use 'default' instead"
      template="default"
    fi
  fi

  # Select config files
  template_example="$EXAMPLES_DIR/$template.md"
  if [ ! -f "$template_example" ]; then
    if [ "$template" != "generic" ]; then
      err_print "No such template found: $template, use 'default' instead"
      template="default"
    fi
    template_example="$default_example"
  fi

  # Allow use configs/.authors/default.md by default
  template_authors="$AUTHORS_DIR/$template.md"
  [ ! -f "$template_authors" ] && template_authors="$default_authors"

  # Allow use configs/.epws/default_{cn,en}.md by default
  template_epws_cn="$EPWS_DIR/${template}_cn.md"
  [ ! -f "$template_epws_cn" ] && template_epws_cn="$default_epws_cn"
  template_epws_en="$EPWS_DIR/${template}_en.md"
  [ ! -f "$template_epws_en" ] && template_epws_en="$default_epws_en"
  template_epws_misc="$EPWS_DIR/${template}_misc.md"
  [ ! -f "$template_epws_misc" ] && template_epws_misc="$default_epws_misc"

  # Customize for different templates
  if [ "$template" = "generic" ]; then
    def_titles=""
    def_headers=""
    def_filename=0
  fi

  # Customize for default.md
  if [ "$(abspath "$f")" = "$(abspath "$default_example")" ]; then
    def_filename=0
    def_failstop=0
    def_gitcheck=0
    def_url_timeout=5
    def_url_max_timeout=10
  fi

  # Allow configure via environment variable or the 2nd argument
  [ -z "$modules" ] && modules="$def_modules"

  # Allow user configure pandoc and more
  [ -z "$pandoc" ] && pandoc=$def_pandoc

  # Allow pass typeset
  [ -z "$typeset" ] && typeset="$def_typeset"

  # Allow pass tabs
  [ -n "$tabs" ] && tabs="$(echo $tabs | tr -d -c '[0-9]')"
  [ -z "$tabs" ] && tabs=$def_tabs
  [ -z "$cols" ] && cols=$def_cols

  # Allow configure timeout
  [ -z "$timeout" ] && timeout=$def_timeout

  # Allow configure failstop
  [ -z "$failstop" ] && failstop=$def_failstop
  # Convert failcontinue to failstop
  env | grep -q ^failcontinue=
  [ $? -eq 0 ] && failstop=$((1-$failcontinue))

  # Allow configure correctaction
  [ -z "$correctaction" ] && correctaction=$def_correctaction
  # Correctaction: 0: ask, 1: stop, 2: commit, 3: next

  [ "$correctaction" = "n" -o "$correctaction" = "next" ] && correctaction=3
  [ "$correctaction" = "C" -o "$correctaction" = "continue" ] && correctaction=3
  [ "$correctaction" = "c" -o "$correctaction" = "commit" ] && correctaction=2
  [ "$correctaction" = "s" -o "$correctaction" = "stop" ] && correctaction=1
  [ "$correctaction" = "q" -o "$correctaction" = "quit" ] && correctaction=1
  [ "$correctaction" = "a" -o "$correctaction" = "ask" ] && correctaction=0

  if [ "$correctaction" != "0" -a  "$correctaction" != "1" -a "$correctaction" != "2" -a "$correctaction" != "3" ]; then
    info_print "correctaction doesn't support '$correctaction', use '0' by default"
    correctaction="0"
  fi
  [ -z "$editor" ] && editor=$(git config --get core.editor 2>/dev/null || echo $def_editor)

  # Allow configure download
  [ -z "$download" ] && download=$def_download

  # Allow configure mermaid
  [ -z "$mermaid" ] && mermaid=$def_mermaid

  # Allow configure registry, npx, npm
  [ -z "$npm_registry" ] && npm_registry="$def_npm_registry"
  [ -z "$npx" ] && npx="npx --prefix $TOP_DIR --registry $npm_registry"
  [ -z "$npm" ] && npm="npm --prefix $TOP_DIR --registry $npm_registry"

  # Override supported suffixes by user input
  [ -z "$suffixes" ] && suffixes="$def_suffixes"

  # Override url_timeout by user input
  [ -z "$url_filters" ] && url_filters="$def_url_filters"
  [ -z "$url_timeout" ] && url_timeout=$def_url_timeout
  [ -z "$url_max_timeout" ] && url_max_timeout=$def_url_max_timeout

  # Allow user override the default titles setting
  [ -z "$titles" ] && titles="$def_titles"
  [ "$titles" = "1" -o "$title" = "1" ] && titles="$def_titles"
  # Let this work like `toc=0`
  [ "$titles" = "0" -o "$title" = "0" ] && titles=""

  # Allow short names
  [ -n "$module" ] && modules="$module"
  [ -n "$argument" ] && arguments="$argument"
  [ -n "$skip" ] && skips="$skip"

  # Customize header values
  [ -z "$headers" ] && headers="$def_headers"
  [ "$headers" = "1" -o "$header" = "1" ] && headers="$def_headers"
  # Let this work like ``toc=0`
  [ "$headers" = "0" -o "$header" = "0" ] && headers=""

  # Examples titles setting
  [ -z "$exam_titles" ] && exam_titles="$def_exam_titles"

  # Default header values
  local h_val=""
  local _v=""
  local v=""
  for h in $headers
  do
    eval v="\${$h}"
    [ -n "$v" ] && continue

    _h=$(echo $h | tr '[A-Z]' '[a-z]')
    eval _v="\${$_h}"

    if [ -z "$_v" ]; then
      # Get author and date
      if [ "$h" = "Author" -o "$h" = "Translator" -o "$h" = "Title" -o "$h" = "Date" ]; then
        if [ "$h" = "Author" -o "$h" = "Translator" ]; then
          user_name="$(git config --get user.name | tr -d '\n')"
          user_email="$(git config --get user.email | tr -d '\n')"
          h_val="$user_name <$user_email>"

          # For translation, the Author should be updated to the original author name
          if [ "$translate" = "1" -a "$h" = "Author" ]; then
            h_val="$def_author"
          fi
        elif [ "$h" = "Title" ]; then
          h_val="$def_title"
        else
          h_val="$(date +'%Y%m%d')"
        fi
      else
        h_val="$(header_value "$template_example" $h | tr -d '\n')"
      fi

      eval `eval echo \\$h=\''$h_val'\'`

      if [ -z "$h_val" ]; then
        eval h_val="\${def_${_h}}"
        eval `eval echo \\$h=\''$h_val'\'`
      fi
    else
      eval `eval echo \\$h=\''$_v'\'`
    fi

    log_print "$h = '$(eval echo \${$h})'"
  done

  # Allow disable gitcheck by default
  [ -z "$gitcheck" ] && gitcheck=$def_gitcheck

  # Allow user override default modules setting
  local m_en
  local def_m_en

  local abstract=0
  local _modules=""
  local _m=""
  local m=""

  for m in $modules
  do
    abstract=0

    # backup old m
    _m="$m"

    is_abstract $m
    if [ $? -eq 0 ]; then
      eval _modules="\${$m}"
      abstract=1
    else
      _modules="$m"
    fi

    for m in $_modules
    do
      is_valid $m $_m
      if [ $? -ne 0 ]; then
        err_print "Invalid module detected: $m, exit"
        info_print "Available modules: $_available_modules"
        exit 1
      fi

      eval def_m_en=\${def_$m}
      eval m_en=\$$m

      if [ -n "$def_m_en" ]; then
        [ -z "$m_en" ] && (($m=$def_m_en))
      fi

      eval def_cm_en=\${def_commit_$m}
      eval cm_en=\${commit_$m}
      if [ -n "$def_cm_en" ]; then
        if [ -z "$cm_en" ]; then
          ((commit_$m=$def_cm_en))
        fi
      fi
    done

    [ $abstract -eq 1 ] && m="$_m"
  done

  return 0
}

# Allow customize template
customize ()
{
  local f="$1"
  local x=""

  for x in default configure
  do
    $x "$f" || return 1
  done

  return 0
}

# Init
init ()
{
  time_begin=$(date +'%s')
  time_init=$(date +'%Y/%m/%d %H:%M:%S')

  info_print "Start running $tool_formal from $tool_repo"
  echo

  return 0
}

# Fini
fini ()
{
  local f="$1"
  local errs="$2"
  local correctors="$3"
  local contributors="$4"
  local time_cost_unit="seconds"

  time_end=$(date +'%s')
  time_cost=$((time_end - time_begin))
  time_fini=$(date +'%Y/%m/%d %H:%M:%S')
  [ "$time_cost" = "0" -o "$time_cost" = "1" ] && time_cost_unit="second"

  stat="$(correct_stat "$f" "$errs")"

  [ -z "$correctors" ] && correctors="$modules"
  [ -z "$contributors" ] && contributors="$modules"
  correctors="$(echo $correctors | sed -e 's/^ //g')"
  contributors="$(echo $contributors | sed -e 's/^ //g')"

  # Do report
  echo
  info_print "Finished running $tool_formal from $tool_repo"

  local t=$(mktemp)
cat <<EOF > $t
- Cost: <$time_cost> $time_cost_unit
- Time: $time_init - $time_fini
- Stat: $stat
- File: [$file_raw]($file_path)
- Tool:
  - document: [$doc_name]($doc_file)
  - template: [$template]($template_example)
  - translate: [$translate]
  - modules: [$modules]
  - correctors: [$correctors]
  - contributors: [$contributors]
EOF

  cat_print "$t" "Running Report" \
    | sed -e 's%\([0-9]* error[s]*\)%\x1b[1;31m\1\x1b[0m%g;' \
    | sed -e 's%\([0-9]* insertion[s]*\)%\x1b[1;32m\1\x1b[0m%g;' \
    | sed -e 's%\([0-9]* deletion[s]*\)%\x1b[1;33m\1\x1b[0m%g;' \
    | sed -e 's%\(<[0-9]*> second[s]*\)%\x1b[1;36m\1\x1b[0m%g;' \
    | sed -e 's%\(nothing to commit\)%\x1b[1;32m\1\x1b[0m%g;' \
    | sed -e 's%\(working tree clean*\)%\x1b[1;33m\1\x1b[0m%g;'

  rm $t

  # Tell users what to do
  if [ $errs -ne 0 ]; then
    local errs_str="Errors"
    [ -z "$errs" -o "$errs" = "0" -o "$errs" = "1" ] && errs_str="Error"
    err_print "$errs_str detected, please fix up, commit and rerun '$tool_raw "$f"'"
  else
    is_corrected "$f"
    if [ $? -eq 0 ]; then
      echo
      info_print "Some issues have been corrected, please review and commit them"
      echo
    fi
  fi

  exit $errs
}

# Run selected modules
modules ()
{
  local f="$1"

  local m
  local m_en
  local errs=0
  local changes_old=0
  local changes_new=0
  local correctors=""
  local contributors=""
  local ret=0
  local sign_line=""

  local _changes_old="$(git diff "$f")"

  local type=""
  local _modules=""
  local _m=""
  local stop=0

  for m in $modules
  do
    abstract=0
    stop=0
    ret=0

    # back up old m
    _m="$m"

    is_abstract $m
    if [ $? -eq 0 ]; then
      eval _modules="\${$m}"
      abstract=1
      info_print "Call [$m] module (modules: $_modules)"
    else
      _modules="$m"
    fi

    for m in $_modules
    do
      # Init return value
      ret=0
      stop=0

      # Allow disable the module simpley with `module_name=0`
      eval m_en=\$$m
      [ "$m_en" = "0" ] && continue

      # Target module must be a function
      type=$(type -t $m)
      [ $? -ne 0 -o "$type" != "function" ] && continue

      # Allow skip many modules
      echo "$skips" | tr ' ' '\n' | grep -E -q "^$m$" && continue

      # Call modules one by one
      info_print "Call [$m] module"

      # Cancel the old sign, to avoid any extract changes on the file itself
      sign_line=$(sign_init "$f")

      changes_old="$(git diff "$f")"
      $m "$f" "$arguments" "$sign_line"
      ret=$?
      changes_new="$(git diff "$f")"

      # Restore the old sign line
      [ -n "$sign_line" ] && sign_fini "$f" "$sign_line"

      # Update correctors and resign it with new module info
      if [ "$changes_new" != "$changes_old" ]; then
        correctors="$correctors $m"
        contributors="$contributors $m"
        # Sign it for commit
        sign_it "$f" "$correctors"
      else
        [ $ret != 0 ] && contributors="$contributors $m"
      fi

      # Report correction status
      stat_print "$f" "$m" "$ret"
      ((errs += ret))

      if [ "$changes_new" != "$changes_old" ]; then
        local t=$(mktemp)
        local cm=0
        eval cm=\${commit_$m}

        # Correctaction: 0: ask, 1: stop, 2: commit, 3: next
        local _correctaction=$correctaction
        if [ "$commit" = "1" -o "$cm" = "1" ]; then
           env | grep -q ^correctaction=
           if [ $? -eq 0 -a "$correctaction" != "2" ]; then
             info_print "Override correctaction=2 for 'commit=1' or 'commit_$m=1'"
           fi
           _correctaction="2"
        fi

        # Let user check the 'diff', if no issue, just 'commit', otherwise, 'edit' it, or 'stop' and even directly run 'next' module.
        while [ "$correctaction" = "0" ];
        do
          ask_print "Auto corrected by [$m] module, what do you want? (0: diff, 1: commit, 2: edit, 3: quit, 4: next, 5: parts, 6: reject) "

          # Let people choose what they want
          [ "$answer" = "c" -o "$answer" = "1" ] && answer="commit"
          [ "$answer" = "s" -o "$answer" = "3" ] && answer="stop"
          [ "$answer" = "q" -o "$answer" = "quit" ] && answer="stop"
          [ "$answer" = "n" -o "$answer" = "4" ] && answer="next"
          [ "$answer" = "p" -o "$answer" = "5" ] && answer="patch"
          [ "$answer" = "r" -o "$answer" = "6" ] && answer="reject"

          [ "$answer" = "d" -o "$answer" = "0" ] && answer="diff"
          [ "$answer" = "e" -o "$answer" = "2" ] && answer="edit"

          [ "$answer" = "reject" ] && git checkout -- "$f" && answer="next"

          [ "$answer" = "commit" -o "$answer" = "patch" -o "$answer" = "stop" -o "$answer" = "next" ] && break

          [ "$answer" = "diff" ] && git diff "$f"
          [ "$answer" = "edit" ] && $editor "$f"
        done

        # Stop it if answer 'stop' or configured as 1
        local patch=""
        [ "$answer" = "stop" -o "$correctaction" = "1" ] && stop=1 && break
        # patch action means commit with --patch
        [ "$answer" = "patch" ] && answer="commit" && patch="--patch"

        if [ "$cm" = "1" -o "$answer" = "commit" -o "$_correctaction" = "2" -o "$commit" = 1 ]; then
          info_print "Commit correct results till to [$m] module"
          if [ "$m" = "images" -a -n "$commit_images" ]; then
            commit "$f $commit_images" "$m" "$patch"
          else
            commit "$f" "$m" "$patch"
          fi
        fi
      fi

      # Stop after failure
      if [ $ret -ne 0 ]; then
        [ -z "$failcontinue" ] && failcontinue=$((1-$failstop))

        [ "$failstop" = "1" -o "$failcontinue" = "0" ] && stop=1 && break
      fi
    done

    # break the whole loop
    [ $stop -eq 1 ] && break

    # Restore normal loop
    if [ $abstract -eq 1 ]; then
      m="$_m"
      info_print "Finished [$m] module (modules: $_modules)"
    fi
  done

  local _changes_new="$(git diff "$f")"

  # Commit based on setting or requirement
  if [ "$_changes_old" != "$_changes_new" -a "$commit" = "1" ]; then
    is_corrected "$f"
    if [ $? -eq 0 ]; then
      info_print "Commit all of the correcting results"
      commit "$f" "all"
    fi
  fi

  fini "$f" $errs "$correctors" "$contributors"
}

profile ()
{
  local shell=""
  echo "$0" | grep -q ${tool_raw}
  if [ $? -eq 0 ]; then
    shell="$(basename /$SHELL | tr -d '-')"
  else
    shell="$(basename /$0 | tr -d '-')"
  fi

  local profile=~/.${shell}rc
  if [ -f $profile ]; then
    echo $profile
  else
    echo ~/.profile
  fi

  return 0
}

tool ()
{
  TOP_DIR="$(absdir "${BASH_SOURCE[0]}")"

  tool_raw="tinycorrect"
  tool_repo=https://gitee.com/tinylab/${tool_raw}
  tool_name="${tool_raw}.sh"
  tool_alias="tico"
  tool_formal="TinyCorrect"
  tool_file="$TOP_DIR/${tool_name}"
  tool_version="$(cat $TOP_DIR/VERSION | cut -d ' ' -f2)"
}

arg0 ()
{
  # Script info
  script_name="$(basename "${BASH_SOURCE[0]}")"
  script_file="$TOP_DIR/$script_name"

  # Examples setting
  EXAMPLES_DIR="$TOP_DIR/examples"
  CONFIGS_DIR="$TOP_DIR/configs"
  AUTHORS_DIR="$CONFIGS_DIR/.authors"
  EPWS_DIR="$CONFIGS_DIR/.epws"

  # Default example and authors
  default_example=$EXAMPLES_DIR/default.md
  default_authors=$AUTHORS_DIR/default.md
  default_epws_cn=$EPWS_DIR/default_cn.md
  default_epws_en=$EPWS_DIR/default_en.md
  default_epws_misc=$EPWS_DIR/default_misc.md

  # Set default document
  doc_name=README.md
  doc_file="$TOP_DIR/$doc_name"
}

arg1 ()
{
  local f="$1"

  # Convert suffixes to types regular expression
  types_rege="($(echo "$suffixes" | tr -s ' ' | tr ' ' '|'))$"

  echo "$f" | grep -q -E "$types_rege"
  [ $? -ne 0 ] && err_print "Only support such file types: $suffixes" && exit 1

  FILE_DIR=$(absdir "$f")

  file_name="$(basename "$f")"
  file_raw="$(rawname "$f")"
  file_path="$FILE_DIR/$file_name"
}

arg2 ()
{
  local f="$1"

  # Detect file template
  def_template="default"
  env | grep -q ^template=
  [ $? -eq 0 ] && return 0

  local t="$FILE_DIR"
  local b="$file_name"
  local d="$(basename $t)"
  t="$t/../"

  if [ "$d" = "articles" -a "$b" != "README.md" -a -d "$t/refs" -a -d "$t/plan" -a -d "$t/meeting" ]; then
    def_template="default"
  else
    def_template="generic"
  fi

  if [ "$(abspath "$f")" = "$(abspath "$default_example")" ]; then
    def_template="default"
  fi
}

args ()
{
  local f="$1"

  for arg in arg0 arg1 arg2
  do
    $arg "$f"
  done
}

install ()
{
  local profile=$(profile)

  if [ ! -f "${tool_file}" ]; then
    info_print "Install: cd /path/to/${tool_raw} && . ${tool_raw}"
    exit 1
  fi

  [ ! -f "$profile" ] && touch $profile
  grep -q "export PATH=\$PATH:$TOP_DIR" $profile
  if [ $? -ne 0 ]; then
    echo "$0" | grep -q ${tool_raw}
    if [ $? -ne 0 ]; then
      info_print "Successfully configured, try '${tool_raw}' or its alias '${tool_alias}'"
    else
      info_print "Successfully configured, do 'source $profile' and try '${tool_raw}' or its alias '${tool_alias}'"
    fi

    sed -i -e "/alias ${tool_alias}=${tool_raw}$/d" $profile
    sed -i -e "\$aexport PATH=\$PATH:$TOP_DIR && alias ${tool_alias}=${tool_raw}" $profile
  fi

  echo "$0" | grep -q ${tool_raw}
  [ $? -ne 0 ] && source $profile

  return 0
}

# ------------------------------
# Main entry of tinycorrect here
# ------------------------------

# main: calling the modules one by one
main ()
{
  local f="$1"

  # Detect and install myself
  install

  # Run main functions
  local x=""
  for x in usage args tools customize init gitcheck modules
  do
    $x "$f" || exit 1
  done
}

# Run main
tool
if [ $# -eq 0 ]; then
  install && usage
else
  main "$src"
fi
